Agent skill
theme-creation
Create new themes for PropertyWebBuilder. Use when creating custom themes, styling websites, or modifying theme templates. Handles theme registration, view templates, CSS, and asset configuration.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/design/theme-creation-etewiah-property-web-builder-fb668e74
SKILL.md
Theme Creation for PropertyWebBuilder
Theme System Overview
PropertyWebBuilder uses a multi-tenant theme system where each website can have its own theme. The system supports:
- Theme inheritance - Child themes extend parent themes
- Page Part Library - 20+ pre-built, customizable sections
- CSS custom properties - Native CSS variables for easy customization
- Per-tenant customization - Each website can override theme defaults
- Custom Liquid tags - Dynamic content rendering in templates
Available Themes (as of Dec 2025)
| Theme | Parent | Status | Description |
|---|---|---|---|
default |
None | Active | Base Tailwind/Flowbite theme |
brisbane |
default | Active | Luxury real estate theme with navy/gold palette |
Key Components
- Theme Registry:
app/themes/config.json- JSON array defining all themes with full configuration - Theme Model:
app/models/pwb/theme.rb- ActiveJSON model with inheritance support - Page Part Library:
app/lib/pwb/page_part_library.rb- Registry of available page parts - Theme Settings Schema:
app/lib/pwb/theme_settings_schema.rb- UI schema for customization - CSS Variables:
app/views/pwb/custom_css/_base_variables.css.erb- Core CSS custom properties - Custom Liquid Tags:
app/lib/pwb/liquid_tags/- Property cards, featured properties, etc. - Theme Directories:
app/themes/[theme_name]/views/- View templates per theme
Theme Resolution Flow
- Request comes in with subdomain (tenant identification)
ApplicationController#set_theme_pathdetermines theme from:- URL parameter
?theme=name(if whitelisted) - Website's
theme_namefield - Fallback to "default"
- URL parameter
- Theme view paths are prepended (child first, then parent)
- Views render from theme directory, falling back through inheritance chain
Creating a New Theme
Step 1: Register the Theme in config.json
Add to app/themes/config.json:
{
"name": "mytheme",
"friendly_name": "My Custom Theme",
"id": "mytheme",
"version": "1.0.0",
"parent_theme": "default",
"description": "A custom theme for my agency",
"author": "Your Name",
"tags": ["modern", "clean"],
"supports": {
"page_parts": [
"heroes/hero_centered",
"heroes/hero_split",
"features/feature_grid_3col",
"testimonials/testimonial_carousel",
"cta/cta_banner"
],
"layouts": ["default", "landing", "full_width"],
"color_schemes": ["light", "dark"],
"features": {
"sticky_header": true,
"back_to_top": true,
"animations": true
}
},
"style_variables": {
"colors": {
"primary_color": {
"type": "color",
"default": "#your-brand-color",
"label": "Primary Color"
},
"secondary_color": {
"type": "color",
"default": "#your-secondary-color",
"label": "Secondary Color"
}
},
"typography": {
"font_primary": {
"type": "font_select",
"default": "Open Sans",
"label": "Primary Font",
"options": ["Open Sans", "Roboto", "Montserrat"]
}
}
},
"page_parts_config": {
"heroes": {
"default_variant": "hero_centered",
"available_variants": ["hero_centered", "hero_split"]
}
}
}
Step 2: Create Directory Structure
mkdir -p app/themes/mytheme/views/layouts/pwb
mkdir -p app/themes/mytheme/views/pwb/welcome
mkdir -p app/themes/mytheme/views/pwb/components
mkdir -p app/themes/mytheme/views/pwb/sections
mkdir -p app/themes/mytheme/views/pwb/pages
mkdir -p app/themes/mytheme/views/pwb/props
mkdir -p app/themes/mytheme/views/pwb/search
mkdir -p app/themes/mytheme/views/pwb/shared
mkdir -p app/themes/mytheme/page_parts # For custom page part templates
Search Page Layout Requirements
IMPORTANT: Search pages MUST follow the responsive layout requirements below.
Desktop Layout (≥1024px / lg breakpoint)
On large screens, search filters MUST be displayed BESIDE search results (side-by-side), NOT above them taking full page width.
+--------------------------------------------------+
| +------------+ +----------------------------+ |
| | Filters | | Search Results | |
| | (1/4) | | (3/4 width) | |
| +------------+ +----------------------------+ |
+--------------------------------------------------+
Required HTML Structure
<!-- Container with flex-wrap -->
<div class="flex flex-wrap -mx-4">
<!-- Sidebar Filters (1/4 on desktop, full on mobile) -->
<div class="w-full lg:w-1/4 px-4 mb-6 lg:mb-0">
<!-- Mobile toggle button (only visible on mobile) -->
<button class="lg:hidden w-full ..."
data-controller="search-form"
data-action="click->search-form#toggleFilters">
Filter Properties
</button>
<!-- Filter form (hidden on mobile, visible on desktop) -->
<div id="sidebar-filters" class="hidden lg:block">
<%= render 'pwb/searches/search_form_for_sale' %>
</div>
</div>
<!-- Search Results (3/4 on desktop, full on mobile) -->
<div class="w-full lg:w-3/4 px-4">
<div id="inmo-search-results">
<%= render 'search_results' %>
</div>
</div>
</div>
Critical Tailwind Classes
| Element | Classes | Purpose |
|---|---|---|
| Container | flex flex-wrap |
Enables side-by-side layout |
| Sidebar | w-full lg:w-1/4 |
100% mobile, 25% desktop |
| Results | w-full lg:w-3/4 |
100% mobile, 75% desktop |
| Filter toggle | lg:hidden |
Only visible on mobile |
| Filter form | hidden lg:block |
Hidden mobile, visible desktop |
Verification Checklist
When creating search pages (buy.html.erb, rent.html.erb):
- Container uses
flex flex-wrap - Sidebar div has
w-full lg:w-1/4 - Results div has
w-full lg:w-3/4 - Test at 1024px width - filters beside results
- Test at 768px width - filters collapse
Reference: See docs/ui/SEARCH_UI_SPECIFICATION.md and docs/ui/SEARCH_LAYOUT_PLAN.md for complete specifications.
Step 3: Copy Files from Parent Theme
Since your theme extends default:
# Only copy files you want to override
cp app/themes/default/views/layouts/pwb/application.html.erb app/themes/mytheme/views/layouts/pwb/
cp app/themes/default/views/pwb/_header.html.erb app/themes/mytheme/views/pwb/
cp app/themes/default/views/pwb/_footer.html.erb app/themes/mytheme/views/pwb/
Step 4: Create Custom CSS Partial
Create app/views/pwb/custom_css/_mytheme.css.erb:
/* Theme: mytheme */
/* Uses CSS custom properties from the base variables system */
<%
# Get theme defaults merged with website overrides
theme = Pwb::Theme.find_by(name: 'mytheme')
defaults = theme&.default_style_variables || {}
styles = defaults.merge(@current_website&.style_variables || {})
primary_color = styles["primary_color"] || "#e91b23"
secondary_color = styles["secondary_color"] || "#3498db"
accent_color = styles["accent_color"] || "#27ae60"
font_primary = styles["font_primary"] || "Open Sans"
font_heading = styles["font_heading"] || "Montserrat"
%>
<%= render partial: 'pwb/custom_css/base_variables',
locals: {
primary_color: primary_color,
secondary_color: secondary_color,
accent_color: accent_color,
font_primary: font_primary,
font_heading: font_heading,
background_color: styles["background_color"] || "#ffffff",
text_color: styles["text_color"] || "#333333",
border_radius: styles["border_radius"] || "8px",
container_width: styles["container_width"] || "1200px"
} %>
<%= render partial: 'pwb/custom_css/component_styles' %>
/* Theme-specific overrides */
.mytheme-theme {
/* Add custom styles here */
}
.mytheme-theme .hero-section {
/* Custom hero styling */
}
Step 5: Update the Layout
Edit app/themes/mytheme/views/layouts/pwb/application.html.erb:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= yield(:page_title) %></title>
<%= yield(:page_head) %>
<%# Tailwind CSS %>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
container: { center: true, padding: 'var(--pwb-container-padding)' },
extend: {
colors: {
primary: 'var(--pwb-primary)',
secondary: 'var(--pwb-secondary)',
accent: 'var(--pwb-accent)',
},
fontFamily: {
sans: ['var(--pwb-font-primary)', 'sans-serif'],
heading: ['var(--pwb-font-heading)', 'serif'],
},
borderRadius: {
DEFAULT: 'var(--pwb-border-radius)',
}
}
}
}
</script>
<%# Flowbite for UI components %>
<link href="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.css" rel="stylesheet" />
<%# Theme styles with CSS variables %>
<style>
<%= custom_styles "mytheme" %>
</style>
<%= javascript_include_tag "pwb/application", async: false %>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js"></script>
<%= csrf_meta_tags %>
</head>
<body class="tnt-body mytheme-theme <%= @current_website&.body_style %> bg-gray-50 text-gray-900">
<div class="flex flex-col min-h-screen">
<%= render partial: '/pwb/header', locals: { not_devise: true } %>
<main class="flex-grow">
<%= render 'devise/shared/messages' %>
<%= yield %>
</main>
<%= render partial: '/pwb/footer', locals: {} %>
</div>
<%= yield(:page_script) %>
</body>
</html>
Step 6: Test the Theme
# Via Rails console
theme = Pwb::Theme.find_by(name: 'mytheme')
theme.view_paths # Verify path resolution
theme.available_page_parts # Check supported page parts
theme.as_api_json # Full theme info
# Update a website to use the theme
website = Pwb::Website.first
website.update(theme_name: 'mytheme')
# Via URL parameter (if enabled)
http://localhost:3000/?theme=mytheme
Page Part Library
Available Categories
| Category | Description | Page Parts |
|---|---|---|
heroes |
Hero sections | hero_centered, hero_split, hero_search |
features |
Feature showcases | feature_grid_3col, feature_cards_icons |
testimonials |
Customer reviews | testimonial_carousel, testimonial_grid |
cta |
Call to action | cta_banner, cta_split_image |
stats |
Statistics | stats_counter |
teams |
Team profiles | team_grid |
galleries |
Image galleries | image_gallery |
faqs |
FAQ sections | faq_accordion |
pricing |
Pricing tables | pricing_table |
Using Page Parts in Templates
{% page_part "heroes/hero_centered" %}
{% page_part "features/feature_grid_3col" %}
{% page_part "cta/cta_banner", style: "primary" %}
Creating Custom Page Part Templates
Create theme-specific page part variants in app/themes/mytheme/page_parts/:
<!-- app/themes/mytheme/page_parts/heroes/hero_custom.liquid -->
<section class="mytheme-hero pwb-hero">
<div class="pwb-container">
<h1 class="pwb-hero__title">{{ page_part.title.content }}</h1>
<p class="pwb-hero__subtitle">{{ page_part.subtitle.content }}</p>
{% if page_part.cta_text.content %}
<a href="{{ page_part.cta_link.content }}" class="pwb-btn--primary">
{{ page_part.cta_text.content }}
</a>
{% endif %}
</div>
</section>
Custom Liquid Tags
Available Tags
<!-- Render a property card -->
{% property_card 123 %}
{% property_card property_id, style: "compact" %}
<!-- Render featured properties -->
{% featured_properties %}
{% featured_properties limit: 6, type: "sale" %}
{% featured_properties limit: 4, style: "card", columns: 4 %}
<!-- Render a contact form -->
{% contact_form %}
{% contact_form style: "compact" %}
{% contact_form style: "inline", property_id: 123 %}
<!-- Embed another page part -->
{% page_part "heroes/hero_centered" %}
{% page_part "cta/cta_banner" %}
CSS Custom Properties System
Base Variables (_base_variables.css.erb)
:root {
/* Colors */
--pwb-primary: <%= primary_color %>;
--pwb-primary-light: color-mix(in srgb, <%= primary_color %> 70%, white);
--pwb-primary-dark: color-mix(in srgb, <%= primary_color %> 70%, black);
--pwb-secondary: <%= secondary_color %>;
--pwb-accent: <%= accent_color %>;
/* Typography */
--pwb-font-primary: <%= font_primary %>;
--pwb-font-heading: <%= font_heading %>;
--pwb-font-size-base: <%= font_size_base %>;
/* Layout */
--pwb-container-width: <%= container_width %>;
--pwb-border-radius: <%= border_radius %>;
/* Spacing */
--pwb-space-xs: 0.25rem;
--pwb-space-sm: 0.5rem;
--pwb-space-md: 1rem;
--pwb-space-lg: 1.5rem;
--pwb-space-xl: 2rem;
}
Component CSS Classes
The system provides ready-to-use component classes:
/* Grid system */
.pwb-grid--2col { grid-template-columns: repeat(2, 1fr); }
.pwb-grid--3col { grid-template-columns: repeat(3, 1fr); }
.pwb-grid--4col { grid-template-columns: repeat(4, 1fr); }
/* Buttons */
.pwb-btn--primary { background-color: var(--pwb-primary); }
.pwb-btn--secondary { background-color: var(--pwb-secondary); }
.pwb-btn--outline { border: 2px solid var(--pwb-primary); }
/* Cards */
.pwb-card { border-radius: var(--pwb-border-radius); box-shadow: var(--pwb-shadow-md); }
/* Heroes */
.pwb-hero { font-family: var(--pwb-font-heading); }
.pwb-hero__title { font-size: 3rem; }
Theme Inheritance
How It Works
Child themes automatically inherit from parent themes:
theme = Pwb::Theme.find_by(name: 'brisbane')
theme.parent_theme # => "default"
theme.parent # => <Pwb::Theme name="default">
theme.inheritance_chain # => [brisbane, default]
theme.view_paths # => [brisbane/views, default/views, app/views]
View Resolution Order
- Check child theme:
app/themes/brisbane/views/ - Check parent theme:
app/themes/default/views/ - Check application:
app/views/
Page Part Resolution
- Check theme's custom page part template
- Check parent theme's template
- Check database-stored PagePart
- Fall back to PagePartLibrary default template
Per-Tenant Customization
Website Style Variables
Each website can override theme defaults:
website = Pwb::Website.first
website.style_variables
# => { "primary_color" => "#ff0000", "font_primary" => "Roboto" }
# Update style variables
website.update(style_variables: {
"primary_color" => "#00ff00",
"secondary_color" => "#333333",
"font_primary" => "Montserrat"
})
Merging with Theme Defaults
theme = Pwb::Theme.find_by(name: website.theme_name)
defaults = theme.default_style_variables
effective_styles = defaults.merge(website.style_variables || {})
Theme Settings Schema
Available Field Types
| Type | Description | Properties |
|---|---|---|
:color |
Color picker | default, css_variable |
:font_select |
Font dropdown | options, default |
:select |
Generic dropdown | options, default |
:range |
Slider | min, max, step, unit |
:toggle |
Boolean switch | default |
Schema Sections
colors- Primary, secondary, accent, background, text colorstypography- Font families, sizes, line heightslayout- Container width, padding, spacingheader- Header style, colorsfooter- Footer style, colors, columnsbuttons- Button styles, sizesappearance- Border radius, shadows, color scheme
Troubleshooting
Theme Not Loading
- Check entry exists in
app/themes/config.json - Verify JSON syntax is valid
- Restart Rails server after config changes
- Check:
Pwb::Theme.find_by(name: 'mytheme')
Styles Not Applying
- Verify CSS variables are defined in
:root - Check body class matches theme name (
.mytheme-theme) - Ensure
custom_styleshelper is called with correct theme name - Clear Rails cache:
Rails.cache.clear
Page Part Not Rendering
- Check template exists:
Pwb::PagePartLibrary.template_exists?(key) - Verify Liquid syntax in template
- Check
block_contentshas data for current locale - Verify page part key is in theme's
supports.page_parts
Inheritance Not Working
- Verify
parent_themeis set correctly in config.json - Check parent theme exists
- Test:
theme.parent.present? - Verify view paths:
theme.view_paths
Examples
Create a luxury theme extending default:
- Add to config.json with
"parent_theme": "default" - Copy only files you need to customize
- Create custom CSS with gold/navy palette
- Set custom font families (Playfair Display, Cormorant Garamond)
Add a new page part variant:
- Create template in
app/themes/mytheme/page_parts/heroes/hero_video.liquid - Add to theme's
supports.page_partsin config.json - Update
page_parts_configwith new variant
Override a specific component:
- Copy file from parent theme to your theme's views directory
- Modify as needed
- Child theme file automatically takes precedence
Creating Color Palettes
Palette File Location
Palettes are stored in separate JSON files per theme:
app/themes/[theme_name]/palettes/
├── classic_red.json
├── ocean_blue.json
├── forest_green.json
└── sunset_orange.json
Palette JSON Structure
Create a new palette file with this structure:
{
"id": "my_palette",
"name": "My Palette",
"description": "A custom color palette",
"preview_colors": ["#primary", "#secondary", "#accent"],
"is_default": false,
"colors": {
"primary_color": "#e91b23",
"secondary_color": "#2c3e50",
"accent_color": "#3498db",
"background_color": "#ffffff",
"text_color": "#333333",
"header_background_color": "#ffffff",
"header_text_color": "#333333",
"footer_background_color": "#2c3e50",
"footer_text_color": "#ffffff",
"light_color": "#f8f9fa",
"link_color": "#e91b23",
"action_color": "#e91b23"
}
}
Dark Mode Support
For explicit dark mode, use the modes structure instead of colors:
{
"id": "modern_dark",
"name": "Modern with Dark Mode",
"supports_dark_mode": true,
"modes": {
"light": {
"primary_color": "#3498db",
"background_color": "#ffffff",
"text_color": "#333333"
},
"dark": {
"primary_color": "#5dade2",
"background_color": "#121212",
"text_color": "#e8e8e8"
}
}
}
If you only provide colors, dark mode is auto-generated using ColorUtils.generate_dark_mode_colors().
Validation & Tools
# Validate all palettes
rake palettes:validate
# List available palettes
rake palettes:list
# Generate CSS with dark mode
rake palettes:css_dark[mytheme,my_palette]
# Check accessibility contrast
rake palettes:contrast[mytheme,my_palette]
# Generate shade scale for a color
rake palettes:shades[#3498db]
Using Palettes in Ruby
loader = Pwb::PaletteLoader.new
light = loader.get_light_colors("mytheme", "my_palette")
dark = loader.get_dark_colors("mytheme", "my_palette")
css = loader.generate_full_css("mytheme", "my_palette") # Includes dark mode
Brisbane Theme Reference (Luxury Theme Pattern)
Color Palette
--luxury-navy: #1a2744;
--luxury-gold: #c9a962;
--luxury-cream: #faf8f5;
Typography
- Headings: Playfair Display (serif)
- Body: Cormorant Garamond (serif)
- Letter spacing: 0.02em for headings
Key Design Elements
- Sharp corners (no border-radius)
- Gold accents on icons and dividers
- Subtle shadows with navy tint
- Hover lift effects
- Decorative gold dividers
Files
app/themes/brisbane/views/layouts/pwb/application.html.erb
app/themes/brisbane/views/pwb/_header.html.erb
app/themes/brisbane/views/pwb/_footer.html.erb
app/themes/brisbane/views/pwb/welcome/index.html.erb
Documentation Reference
For complete documentation, see:
docs/architecture/COLOR_PALETTES_ARCHITECTURE.md- Color palette systemdocs/11_Theming_System.md- Full theming system documentationdocs/08_PagePart_System.md- Page part system detailsapp/lib/pwb/page_part_library.rb- Page part definitionsapp/lib/pwb/theme_settings_schema.rb- Settings schemaapp/themes/shared/color_schema.json- Palette JSON schemaapp/services/pwb/palette_loader.rb- Palette loading serviceapp/services/pwb/color_utils.rb- Color utilities
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?