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 %>