RRUI-008 · Link helpers · Icon slot

rui_tel / rui_mailto / rui_sms drop the icon slot block

Docs and Lookbook show with_icon on communication helpers. Standalone rui_link forwards the block and renders the icon. The same block on rui_tel (and mailto/sms) never reaches the Link component — Ruby discards it because the helper neither takes nor forwards &block.

Open gem 0.58.1 2026-08-17 same class as RRUI-003, ViewHelper path

USAGE documents the block; the helper ignores it. Link USAGE: “All three accept the same styling params as rui_link and support icon slots,” with a rui_tel(…) do |link| link.with_icon(:phone_call) end example. rui_link already does rapidui_component …, &block. The three communication wrappers do not.

Summary

Surface ViewHelper#rui_tel, #rui_mailto, #rui_sms
Works today rui_link(…) — forwards █ color: / variant: / size: on tel/mailto/sms kwargs (those are **options)
Broken today Icon (and any other) slot set in the helper block — never applied
Impact Phone / email / SMS links cannot show Lucide icons via the documented API

Not a bug (checked alongside)

color: :muted does apply on rui_tel. :muted is a valid ColorBuilderHelper key (defaults to Tailwind gray). With variant: :ghost the link gets the ghost palette, e.g. text-gray-700 hover:bg-gray-100 (and dark counterparts). If muted looks “not muted,” that is the gray-700 / 300 shade table — same steps rui_text(color: :muted) uses — not a dropped kwarg.

Repro

<%= rui_tel(
  vendor_phone,
  vendor_phone,
  size: :sm,
  variant: :ghost,
  color: :muted,
  aria: { label: "Call vendor" }
) do |link| %>
  <% link.with_icon(:phone) %>
<% end %>
  1. Actual: Phone number text only — no SVG in the DOM. Color classes for muted/ghost are present.
  2. Expected: Same as rui_link — leading phone icon, inheriting currentColor from the muted/ghost link.

Contrast — this works:

<%= rui_link(vendor_phone, "tel:#{vendor_phone}", size: :sm, variant: :ghost, color: :muted) do |link| %>
  <% link.with_icon(:phone) %>
<% end %>

Evidence (0.58.1)

rui_link forwards the block

def rui_link(*args, **kwargs, &block)
  if block_given? && args.length == 1 && args[0].present?
    args = [nil, args[0]]
  end
  rapidui_component Link::Component, *args, **kwargs, &block
end

rui_tel does not

def rui_tel(text, number, country_code: nil, **options)
  phone = number.to_s
  # …country_code normalize…
  rapidui_component Link::Component, text, "tel:#{phone}", **options
  # ← no &block
end

Same gap on rui_mailto and rui_sms. Ruby silently discards the caller’s block. rapidui_component already accepts &block and passes it to render.

Fix in the gem

def rui_tel(text, number, country_code: nil, **options, &block)
  phone = number.to_s
  if country_code.present? && !phone.start_with?("+", "00")
    code = country_code.to_s.gsub(/^\+/, "")
    phone = "+#{code}#{phone}"
  end
  rapidui_component Link::Component, text, "tel:#{phone}", **options, &block
end
Same &block forward for rui_mailto and rui_sms.
  1. Parity test: rui_tel("Call", "+15551212") { |l| l.with_icon(:phone) } asserts SVG (or icon markup) plus href="tel:+15551212".
  2. Same for mailto/sms. Do not assume the RRUI-003 FormBuilder suite covers ViewHelper wrappers.

Done when