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_section reveals a dashed border and a sticky “Edit” button
  • Clicking Edit opens the section’s field editor in a new tab
  • editable_content fields 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.