Card

A flexible, plug-and-play card system. Every slot is optional — skip any piece and the card still looks right.

Key Features

  • Six independent slotsmedia, icon, header, body, list, footer
  • Fixed render order — slots always appear in the same sequence regardless of call order
  • Shortcut APItitle:/description:/icon: for the common icon + heading + description case
  • Whole-card link modehref: wraps the card in a single <a> with hover lift
  • Four variants:elevated, :outline, :flat, :soft
  • Semantic colors — icon tiles, soft variant, footer links resolve through config.colors
  • No JS controller — pure server-rendered, dark mode included
  • Starter tier — available on all RRUI licenses

Basic Usage

Pass a block and populate slots with with_* calls. Slots render in a fixed order: media → icon/header → body → divider → list → footer.

Timed Online

A flexible online sale format that allows buyers more time to review and bid.
Basic Usage
<%= rui_card do |c| %>
  <% c.with_icon(name: "clock") %>
  <% c.with_header(title: "Timed Online") %>
  <% c.with_body do %>
    A flexible online sale format that allows buyers more time to review and bid.
  <% end %>
<% end %>

Slots

All slots are optional. Order is fixed regardless of the order you call with_* in.

Icon Slot

A tinted rounded-square tile. Accepts a Lucide name: or a content block for custom markup. placement: :top (default, own row) or :inline (beside the header).

Top placement

Icon tile on its own row above the header.

Inline placement

Icon tile beside the header.

Icon Slot
<% c.with_icon(name: "clock") %>
<% c.with_icon(name: "headphones", color: :warning, placement: :inline) %>
<% c.with_icon do %>
  <span class="text-2xl font-bold">42</span>
<% end %>

Header Slot

title: is required in-slot. Optional subtitle: and as: heading tag override (default :h3).

Strong sale-day support

Dedicated support from listing to final result.

Our team is with you every step of the way.
Header Slot
<% c.with_header(title: "Timed Online") %>
<% c.with_header(title: "Support", subtitle: "Dedicated support from listing to final result.") %>
<% c.with_header(title: "Section", as: :h2) %>

Body Slot

Muted description text. Use the text: param or a block — same block-or-text pattern as Alert's message slot.

Livestock Selling

Full-service selling for producers, from listing to settlement.
Body Slot
<% c.with_body(text: "A flexible online sale format.") %>
<% c.with_body do %>
  A flexible online sale format that allows buyers more time to review and bid.
<% end %>

List Slot

Checklist rows with circled-icon tiles. with_list yields itself so you can call with_item repeatedly. A divider renders above the list automatically when a header or body precedes it.

Timed Online

A flexible online sale format.

  • Keeps buyers active with outbid alerts
  • Set and forget max bid functionality
  • Custom icon per item
List Slot
<% c.with_list do |l| %>
  <% l.with_item(text: "Keeps buyers active with outbid alerts") %>
  <% l.with_item(text: "Set and forget max bid functionality") %>
  <% l.with_item(text: "Custom icon per item", icon: "star", color: :warning) %>
<% end %>

Media Slot

An image flush to the card's top edge. Corners follow the card's shape: via overflow-hidden on the root.

Olive Grove yards

Olive Grove Sale

On-property preview this Saturday.
Media Slot
<% c.with_media(src: "/sales/olive-grove.jpg", alt: "Olive Grove yards", aspect: :video) %>

Key/Value List Rows (v0.58.0 / v0.58.1)

trailing: pushes content to a list item's right edge; leader: (list-level) draws a decorative hairline rule filling the gap. icon: false drops the icon tile span entirely, for rows that have no icon at all — a fee table, spec sheet, or pricing tier row.

Selling fees


  • 1 to 250$7.80
  • 251 to 1,000$6.20
  • 1,001+$4.90
Key/Value List Rows
<%= rui_card do |c| %>
  <% c.with_header(title: "Selling fees") %>
  <% c.with_list(variant: :pill, leader: true) do |l| %>
    <% l.with_item(text: "1 to 250", icon: false, trailing: "$7.80") %>
    <% l.with_item(text: "251 to 1,000", icon: false, trailing: "$6.20") %>
    <% l.with_item(text: "1,001+", icon: false, trailing: "$4.90") %>
  <% end %>
<% end %>

trailing: accepts a plain string or a block for markup (the block wins if both are given) — l.with_item(text: "Status") { rui_badge(text: "Active", color: :success) }.

leader: is list-level — one flag styles every row — and off by default. Per item, the rule only draws when that item also has trailing: set; leader: true with no trailing: skips the rule for that row rather than raising, since a rule to nowhere is meaningless. It's decorative (aria-hidden="true"), so a screen reader reads the label then the value with nothing between.

icon: false (or icon: nil, treated identically) renders the row with no icon tile <span> in the DOM at all — not an empty one, not a transparent one. Omitting icon: still defaults to "check", and any truthy icon name renders byte-identical to pre-0.58.1.

Content & Divider Overrides (v0.57.0)

content_class: merges onto the card's content wrapper; divider_class: merges onto the divider rule. Both go through tailwind_merge, so the conflicting default is dropped from the class attribute rather than being out-ordered by it. Both are nil by default.

Agents

A streamlined solution that supports relationships.

  • Efficient listing tools
  • National buyer reach

The gap either side of the divider is not a divider property. It is the content wrapper's inter-slot gap-4, which is why tightening it rides on content_class (gap-3) rather than divider_class. content_class is also the only way to reach a padding value between the none/sm/md/lg tiers (0/16/24/32px).

Content & Divider Overrides
<%= rui_card(content_class: "p-5 gap-3", divider_class: "border-sky-200") do |c| %>
  <% c.with_header(title: "Agents") %>
  <% c.with_body(text: "A streamlined solution that supports relationships.") %>
  <% c.with_list do |l| %>
    <% l.with_item(text: "Efficient listing tools", icon: "clipboard-list") %>
  <% end %>
<% end %>

Card Options

Card-level options control the surface, shape, padding, and semantic color.

:elevated

White bg, border, shadow-sm (default).

:outline

Transparent bg, visible border, no shadow.

:flat

Solid muted background, no shadow.

:soft

Background tinted from color:.
Card Options
rui_card(
  variant: :elevated,   # :elevated | :outline | :flat | :soft
  shape: :rounded,      # :rounded | :square | :pill
  padding: :md,         # :none | :sm | :md | :lg
  color: :primary,      # semantic color for icon / soft / footer / list
  href: nil,            # whole-card link mode
  divider: true,        # divider above list slot
  class: nil
) do |c|
  # ...slots
end

Whole-Card Link

When href: is set, the root element becomes an <a> with hover shadow lift. aria-label is set from the header title or shortcut title: param. The footer degrades to styled text — never a nested <a>.

Whole-Card Link
<%= rui_card(href: "/lots/482") do |c| %>
  <% c.with_header(title: "Lot 482 — Angus Steers") %>
  <% c.with_body(text: "45 head, 380-420kg, Wagga Wagga NSW.") %>
  <% c.with_footer(text: "View lot", href: "/lots/482") %>
<% end %>

Shortcut API

For the common icon + heading + description case, skip the block entirely. href: combines with the shortcut too.

Advertise with Us

Reach the people driving Australian agriculture
Shortcut API
<%= rui_card(title: "Advertise with Us", description: "Reach the people driving Australian agriculture", icon: "megaphone") %>

<%= rui_card do |c| %>
  <% c.with_icon(name: "megaphone") %>
  <% c.with_header(title: "Advertise with Us") %>
  <% c.with_body(text: "Reach the people driving Australian agriculture") %>
<% end %>

Reference Recipes

Four common card shapes from real-world usage.

Full card

Icon tile → heading → description → divider → checklist → footer bar.

Timed Online

A flexible online sale format that allows buyers more time to review and bid.

  • Keeps buyers active with outbid alerts
  • Set and forget max bid functionality

Compact horizontal

Icon tile inline beside title and description. No divider, no footer.

Strong sale-day support

Dedicated support from listing to final result.

Service CTA card

Header + body + highlights list + CTA footer — no icon tile.

Livestock Selling

Full-service selling for producers, from listing to settlement.

  • National buyer network
  • Weekly market reports

API Reference

rui_card

Flexible card with six optional slots and a shortcut API for icon + heading + description

Parameter Type Default Description
title String Shortcut API — renders header title when no block slot is set
description String Shortcut API — renders body text when no block slot is set
icon String/Symbol Shortcut API — Lucide icon name for the icon tile

Appearance

Visual styling options

Parameter Type Default Description
variant Symbol :elevated Card surface style
:elevated :outline :flat :soft
shape Symbol :rounded Corner radius
:rounded :square :pill
padding Symbol :md Inner padding
:none :sm :md :lg
color Symbol :primary Semantic color for icon tile, soft variant, footer link, and list tiles
divider Boolean true Show divider above list when header or body precedes it
class String Additional root classes merged via tailwind_merge

Linking

Whole-card link mode

Parameter Type Default Description
href String Wraps the whole card in a single <a> with hover shadow lift and aria-label from title

Key/Value List Rows (v0.58.0 / v0.58.1)

with_item(trailing:) and with_list(leader:) build label/value rows with a rule between; with_item(icon: false) drops the icon tile entirely for rows that have no icon at all

Parameter Type Default Description
trailing String with_item(trailing:) — content pushed to the row's right edge. Plain string or a block for markup (the block wins if both are given, same contract as Table::Row#with_cell/Kanban::Column#with_card). nil renders no trailing span, byte-identical to pre-0.58.0.
leader Boolean false with_list(leader:) — draws a decorative hairline rule (aria-hidden) filling the gap between an item's text and its trailing: content, on every row in that list. Per item, the rule only draws when that item also has trailing: set — leader: true with no trailing: skips the rule for that row rather than raising.
icon String/Symbol/false/nil "check" with_item(icon:) — false (or nil, treated identically) renders the row with NO icon tile <span> in the DOM at all, not an empty or transparent one. Any truthy icon name (including the default) renders byte-identical to pre-0.58.1. Before 0.58.1, icon: nil rendered a '?' placeholder — that was a bug, not a documented affordance.

Content & Divider Overrides (v0.57.0)

Reach the content wrapper's padding/gap and the divider rule from the call site — nil by default, byte-identical to pre-0.57.0 output

Parameter Type Default Description
content_class String Merges onto the content wrapper via tailwind_merge. Reaches the padding (the none/sm/md/lg tier scale is 0/16/24/32px, so in-between values were unreachable) and the fixed gap-4, which is what actually controls the space either side of the divider. e.g. "p-5 gap-3".
divider_class String Merges onto the divider <hr> via tailwind_merge. Before 0.57.0 DIVIDER_CLASSES was a hardcoded literal, so the rule's colour and weight could not be changed at all. e.g. "border-sky-100 border-t-2".

Slots

Content slots for customizing component parts

Slot Description
icon with_icon(name:, color:, placement: :top/:inline) — tinted Lucide tile or custom block content
header with_header(title:, subtitle:, as: :h3) — heading and optional subtitle
body with_body(text:) or block — muted description text
list with_list(leader:) { |l| l.with_item(text:, icon:, color:, trailing:) } — checklist rows with circled icons; icon: false drops the tile, trailing:/leader: build key/value rows
media with_media(src:, alt:, aspect: :video/:square/:wide/:none) — image flush to top edge
footer with_footer(text:, href:, arrow: :right/:left/:none, class:, wrapper_class:) or block — full-width footer bar