RRUI-004 · Carousel · Indicators

Carousel indicators inject empty space under the viewport

With show_indicators: true and an overlay indicator position (hero / banner defaults), the carousel grows a cream/empty band under the slides. Turning indicators off removes the gap. Overlay dots can also sit in that band instead of on the image.

Open gem 0.54.2 2026-08-12 reported from StockLive hero

Empty nav row always mounts when indicators are on. The outside-nav wrapper is gated on @show_indicators, not on “outside indicators or outside arrows actually need a row.” Overlay indicators still open that branch, so an empty flex … mt-4 row is rendered under the viewport.

Summary

Component RapidRailsUI::Carousel::Component
Trigger show_indicators: true + indicator_position:outside (e.g. :hero:overlay_bottom_right)
Symptom Full-width empty strip under the carousel; disappears when indicators are disabled
Side effect Absolute overlay dots are positioned against the root (which now includes the empty row), so they can appear in/near the gap instead of over the slide

Repro

<%= rui_carousel(
  id: "hero-carousel",
  variant: :hero,          # indicator_position: :overlay_bottom_right
  show_indicators: true,   # default
  show_arrows: false       # hero default
) do |c| %>
  <% c.with_slide(label: "A") do %>…<% end %>
  <% c.with_slide(label: "B") do %>…<% end %>
<% end %>
  1. Actual: Empty band under the image (~ mt-4 row height); dots often sit in that band at bottom-right.
  2. Set show_indicators: false — gap gone.
  3. Expected: Overlay indicators only; no in-flow nav row; root height = viewport height.

Evidence (0.54.2)

Outside-nav wrapper opens for any indicators

<% if (@show_arrows && !arrows_inside?) || @show_indicators %>
  <div class="flex items-center justify-center gap-4 mt-4">
    <%# arrows only if arrow_position == :outside %>
    <%# indicators only if indicator_position == :outside %>
  </div>
<% end %>

For :hero / :banner: arrows are not outside, indicators are overlay — so the inner branches render nothing, but the wrapper + mt-4 still do.

Overlay indicators render as a sibling of that row

<% if @show_indicators && … && @indicator_position != :outside %>
  <div class="absolute bottom-4 right-4 …">…dots…</div>
<% end %>

Root is relative. Absolute bottom is relative to the whole root — viewport plus the empty mt-4 row — so dots can land in the injected space.

Fix in the gem

  1. Only open the outside-nav row when it has content:
<% if (@show_arrows && !arrows_inside?) ||
      (@show_indicators && @indicator_position == :outside) %>
Or skip rendering the wrapper when both inner branches are empty.
  1. Optionally render overlay indicators inside the viewport (or a dedicated relative media frame) so absolute bottom-* is always relative to the slide plane, not a taller root.
  2. Add a regression: variant: :hero / overlay indicators → assert no empty outside-nav node and no extra in-flow height under the viewport.

Done when