Content Types
Every field in Lean CMS has a content_type that determines how it is stored, edited, and rendered. Nine types are available.
text
A plain single-line string. The most common type for headings, labels, and short copy.
YAML definition:
hero:
heading:
type: text
default: "Welcome to Our Site"
Rendering:
<h1><%= page_content(@page, 'hero', 'heading') %></h1>
Editor experience: Single-line text input. Supports optional max_length.
rich_text
Multi-paragraph HTML content, edited with a Trix rich text editor (Action Text).
YAML definition:
about:
body:
type: rich_text
default: "<p>We've been building things since 2005.</p>"
Rendering:
<div class="prose">
<%= page_content_html(@page, 'about', 'body') %>
</div>
Use page_content_html (not page_content) so the Action Text HTML is rendered safely without double-escaping.
image
A file attachment managed through ActiveStorage. Can store an uploaded file or fall back to a URL string.
YAML definition:
hero:
background:
type: image
Rendering:
<% bg = page_content_image_url(@page, 'hero', 'background') %>
<% if bg %>
<%= image_tag bg, class: "w-full h-64 object-cover" %>
<% end %>
With a variant:
<% thumb = page_content_image_url(@page, 'team', 'photo', variant: { resize_to_fill: [400, 400] }) %>
Editor experience: File upload with drag-and-drop. Accepts JPG, PNG, GIF, WebP.
boolean
A true/false toggle. Stored as the string "true" or "false".
YAML definition:
about:
show_team_section:
type: boolean
default: "true"
Rendering:
<% if page_content?(@page, 'about', 'show_team_section') %>
<%= render 'team_section' %>
<% end %>
Use page_content? which returns a Ruby boolean. page_content returns the raw string.
url
A URL string. Displayed as a plain text field with URL validation.
YAML definition:
hero:
cta_url:
type: url
default: "/contact"
Rendering:
<a href="<%= page_content(@page, 'hero', 'cta_url') %>">
<%= page_content(@page, 'hero', 'cta_label') %>
</a>
color
A CSS color value (hex, rgb, named). Displayed as a color picker in the editor.
YAML definition:
branding:
primary_color:
type: color
default: "#2563eb"
Rendering:
<div style="background-color: <%= page_content(@page, 'branding', 'primary_color') %>">
Or with Tailwind’s JIT arbitrary values:
<div class="bg-[<%= page_content(@page, 'branding', 'primary_color') %>]">
dropdown
A string value restricted to a predefined list of options. Options are defined in the YAML or in the content record’s options attribute.
YAML definition:
hero:
layout_style:
type: dropdown
options:
- "centered"
- "left-aligned"
- "split"
default: "centered"
Rendering:
<section class="hero hero--<%= page_content(@page, 'hero', 'layout_style') %>">
Editor experience: A <select> element showing the defined options.
cards
A JSON array of card objects. Each card can have a title, description, icon, background color, image, and link. Rendered via the cards_section component.
YAML definition:
services_preview:
cards:
type: cards
default: []
Rendering with the component (recommended):
<%= cards_section('services_preview', grid_cols: 3) %>
Rendering manually:
<% page_cards(@page, 'services_preview').each do |card| %>
<div class="card">
<h3><%= card['title'] %></h3>
<p><%= card['description'] %></p>
</div>
<% end %>
Component options:
| Option | Default | Description |
|---|---|---|
grid_cols |
3 |
Number of columns in the grid |
gap |
8 |
Tailwind gap value |
card_class |
"bg-white rounded-xl p-8 shadow-sm..." |
CSS classes for each card |
icon_size |
12 |
Icon size (Tailwind w/h value) |
scroll_animate |
false |
Animate cards in on scroll |
stagger_delay |
150 |
Milliseconds between card animations |
Editor experience: A drag-to-reorder card manager with fields for title, description, icon, color, image, and link.
bullets
A JSON array of bullet point objects. Each bullet has a title and optional description. Rendered via the bullets_section component.
YAML definition:
why_choose_us:
bullets:
type: bullets
default: []
Rendering with the component (recommended):
<%= bullets_section('why_choose_us') %>
Rendering manually:
<% page_bullets(@page, 'why_choose_us').each do |bullet| %>
<div class="flex gap-3">
<span class="text-green-500">✓</span>
<div>
<strong><%= bullet['title'] %></strong>
<p><%= bullet['description'] %></p>
</div>
</div>
<% end %>
Editor experience: Add/remove/reorder bullet points with a title and description per item.