Helper API

All helpers are available in your views after including LeanCms::PageContentHelper in ApplicationHelper.


Content retrieval

page_content

Retrieve a single field value from the database (cached).

page_content(page, section, key, default: nil)
Parameter Type Description
page LeanCms::Page or String The page — pass @page or a slug string like 'home'
section String The section name
key String The field key
default: Any Value to return if no content is stored

Examples:

<h1><%= page_content(@page, 'hero', 'heading') %></h1>
<h1><%= page_content('home', 'hero', 'heading', default: 'Welcome') %></h1>

Returns: The stored value for the field’s content type — a String for text, url, color, dropdown; an ActiveStorage attachment for image; an Array for cards and bullets; a Boolean for boolean.


page_content_html

Render rich text or HTML content safely.

page_content_html(page, section, key, default: nil)

Example:

<div class="prose">
  <%= page_content_html(@page, 'about', 'body') %>
</div>

Use this instead of page_content for rich_text fields. It handles ActionText objects and sanitizes plain HTML strings.


page_content_image_url

Get an ActiveStorage blob or URL for an image field.

page_content_image_url(page, section, key, variant: nil)

Examples:

<%# Full-size image %>
<%= image_tag page_content_image_url(@page, 'hero', 'background') %>

<%# With an ActiveStorage variant %>
<%= image_tag page_content_image_url(@page, 'team', 'photo', variant: { resize_to_fill: [400, 400] }) %>

Returns nil if no image is attached and no URL is stored. Always check for nil before rendering:

<% bg = page_content_image_url(@page, 'hero', 'background') %>
<% if bg %>
  <%= image_tag bg, class: "..." %>
<% end %>

page_content?

Check a boolean field. Returns a Ruby true or false.

page_content?(page, section, key, default: false)

Example:

<% if page_content?(@page, 'about', 'show_team_section') %>
  <%= render 'team_section' %>
<% end %>

page_section

Get all fields for a section as a Hash.

page_section(page, section)
# => { 'heading' => 'Welcome', 'subheading' => 'We build things.' }

Useful when you need multiple fields from the same section without making separate calls:

<% hero = page_section(@page, 'hero') %>
<h1><%= hero['heading'] %></h1>
<p><%= hero['subheading'] %></p>

page_structure

Get all content for a page grouped by section.

page_structure(page)
# => { 'hero' => { 'heading' => '...', 'subheading' => '...' }, 'about_preview' => { ... } }

page_cards

Get the cards array for a section.

page_cards(page, section)
# => [{ 'title' => '...', 'description' => '...', 'icon' => '...' }, ...]

Prefer cards_section for rendering; use page_cards when you need the raw data.


page_bullets

Get the bullets array for a section.

page_bullets(page, section)
# => [{ 'title' => '...', 'description' => '...' }, ...]

Rendering components

editable_content

Render a field value wrapped in inline-edit controls for CMS users. Regular visitors see a plain HTML element.

editable_content(section, key, default: nil, tag: :span, page: nil, **html_options)
Parameter Default Description
section Section name
key Field key
default: nil Fallback if no value stored
tag: :span HTML wrapper tag
page: @page Override the implicit page

Examples:

<%# Simple inline-editable heading %>
<h1><%= editable_content('hero', 'heading') %></h1>

<%# With tag and default %>
<p><%= editable_content('hero', 'subheading', tag: :p, default: 'Your subheading here') %></p>

<%# Override page %>
<%= editable_content('hero', 'heading', page: @other_page) %>

cards_section

Render a cards section using the LeanCms::CardsSectionComponent.

cards_section(section, page: nil, **options)
Option Default Description
grid_cols 3 Number of grid columns
gap 8 Tailwind gap value
card_class 'bg-white rounded-xl p-8 shadow-sm hover:shadow-md transition-shadow' CSS classes per card
icon_size 12 Icon w/h (Tailwind scale)
icon_shape 'lg' Icon container border radius
icon_bg_gradient false Use gradient on icon background
text_align nil Override text alignment
scroll_animate false Animate cards in on scroll
stagger_delay 150 Milliseconds between card animations

Example:

<%= cards_section('services_preview', grid_cols: 3, scroll_animate: true) %>

bullets_section

Render a bullets section using the LeanCms::BulletsSectionComponent.

bullets_section(section, page: nil, **options)

Example:

<%= bullets_section('why_choose_us') %>

Edit overlays

cms_editable_section

Wrap a block of content with a hover-activated edit overlay for CMS users. Invisible to visitors.

cms_editable_section(page:, section:, display_title: nil, &block)
Parameter Description
page: Page slug string (e.g. 'home')
section: Section name
display_title: Human-readable label shown in the overlay. Defaults to "Page - Section"

Example:

<%= cms_editable_section(page: 'home', section: 'hero', display_title: 'Hero Section') do %>
  <section class="hero">
    <h1><%= page_content(@page, 'hero', 'heading') %></h1>
  </section>
<% end %>

lean_cms_picture_tag

Render a responsive <picture> element for a static layout image processed by the lean_cms:optimize_images rake task. Emits a WebP <source> and a same-format fallback <img>, both with srcset for the configured widths, lazy-loaded and async-decoded by default.

lean_cms_picture_tag(name, alt:, widths: [640, 1280, 1920], format: :jpg, sizes: "100vw", **img_options)
Option Default Description
name Base filename (no extension, no width). The optimizer produces <name>-<width>.webp and <name>-<width>.<format> under app/assets/images/.
alt: <img> alt text. Required.
widths: [640, 1280, 1920] Widths to include in srcset. Must match what the optimizer produced.
format: :jpg Fallback format. Use :png for source images with transparency.
sizes: "100vw" The HTML sizes attribute. Override per-image based on layout.
**img_options Any other attributes passed through to the inner <img> tag (e.g. class:, loading:, fetchpriority:).

Setup: place originals in app/assets/images/source/, then run:

rails lean_cms:optimize_images

Outputs land in app/assets/images/. The task is idempotent (skips outputs newer than source).

Examples:

<%# Hero image, responsive across viewport widths %>
<%= lean_cms_picture_tag "wire-panel",
      alt: "Control panel wiring",
      widths: [640, 1280],
      sizes: "(min-width: 768px) 448px, 100vw",
      class: "rounded-2xl shadow-lg max-w-md w-full object-cover" %>

<%# Above-the-fold nav logo — eager-load + fetchpriority high %>
<%= lean_cms_picture_tag "site-logo",
      alt: "Company logo",
      widths: [640],
      sizes: "200px",
      loading: "eager",
      fetchpriority: "high",
      class: "h-12 w-auto" %>

cms_settings_section

Same as cms_editable_section but the overlay Edit button links to the Settings admin page instead of the page content editor. Use this for settings-backed content like contact info and business hours.

cms_settings_section(display_title:, anchor: nil, &block)

Example:

<%= cms_settings_section(display_title: 'Contact Information', anchor: 'site-info') do %>
  <p><%= LeanCms::Setting.site_phone %></p>
<% end %>