Your First Page
This walkthrough builds a complete home page — from defining its content structure to rendering it with your own HTML and Tailwind CSS.
1. Define the structure in YAML
In config/lean_cms_structure.yml, add your home page sections:
pages:
home:
display_title: "Home"
page_order: 1
sections:
hero:
display_title: "Hero"
section_order: 1
fields:
heading:
type: text
default: "Custom Fabrication & Machining"
subheading:
type: text
default: "Precision parts, fast turnaround, no minimums."
cta_label:
type: text
default: "Request a Quote"
cta_url:
type: url
default: "/contact"
background:
type: image
about_preview:
display_title: "About Preview"
section_order: 2
fields:
body:
type: rich_text
default: "<p>We've been building precision parts since 2005...</p>"
show_link:
type: boolean
default: "true"
2. Seed the database
rails lean_cms:load_structure
This creates rows in lean_cms_page_contents for each field defined above. Running it again is safe — it uses find_or_create_by so existing values are not overwritten.
3. Load the page in your controller
class PagesController < ApplicationController
def home
@page = LeanCms::Page.find_by!(slug: "home")
# Eager-load all content to avoid N+1 queries
@page.page_contents.load
end
end
4. Build your ERB template
<%# app/views/pages/home.html.erb %>
<%= cms_editable_section(page: 'home', section: 'hero', display_title: 'Hero') do %>
<section class="relative min-h-screen flex items-center">
<%# Background image %>
<% bg = page_content_image_url(@page, 'hero', 'background') %>
<% if bg %>
<%= image_tag bg, class: "absolute inset-0 w-full h-full object-cover" %>
<% end %>
<div class="relative z-10 max-w-4xl mx-auto px-6 text-white">
<h1 class="text-5xl font-bold mb-4">
<%= page_content(@page, 'hero', 'heading') %>
</h1>
<p class="text-xl mb-8">
<%= page_content(@page, 'hero', 'subheading') %>
</p>
<a href="<%= page_content(@page, 'hero', 'cta_url') %>"
class="btn btn-primary">
<%= page_content(@page, 'hero', 'cta_label') %>
</a>
</div>
</section>
<% end %>
<%= cms_editable_section(page: 'home', section: 'about_preview', display_title: 'About Preview') do %>
<section class="py-24 max-w-3xl mx-auto px-6">
<div class="prose prose-lg">
<%= page_content_html(@page, 'about_preview', 'body') %>
</div>
<% if page_content?(@page, 'about_preview', 'show_link') %>
<a href="/about" class="mt-6 inline-flex items-center font-semibold text-blue-600">
Learn more →
</a>
<% end %>
</section>
<% end %>
5. Inline editable fields (optional)
For fields you want editors to edit directly on the page without opening a modal, use editable_content instead of page_content:
<h1 class="text-5xl font-bold">
<%= editable_content('hero', 'heading') %>
</h1>
When a logged-in CMS editor clicks the heading, it becomes an editable field inline. Visitors see a normal <h1> with no extra markup.
What it looks like for editors
When a CMS editor (logged-in user where has_any_cms_permission? returns true) views the page:
- Hovering over any
cms_editable_sectionreveals a dashed border and a sticky “Edit” button - Clicking Edit opens the section’s field editor in a new tab
editable_contentfields are clickable directly on the page
Visitors see none of this — the wrappers render as plain HTML with no extra attributes or overhead.
Helper quick reference
| Helper | Returns | Use for |
|---|---|---|
page_content(@page, section, key) |
String / ActiveStorage blob | Rendering any field value |
page_content_html(@page, section, key) |
Safe HTML | Rich text fields |
page_content_image_url(@page, section, key) |
URL / blob | Image src attributes |
page_content?(@page, section, key) |
Boolean | Conditional rendering |
editable_content(section, key) |
Rendered HTML | Inline-editable text |
cms_editable_section(page:, section:) |
Block wrapper | Section-level edit overlay |
Full signatures in the Helper Reference.