Claude Code Plugins

Community-maintained marketplace

Feedback

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 Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name theme-creation
description 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.

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

  1. Theme Registry: app/themes/config.json - JSON array defining all themes with full configuration
  2. Theme Model: app/models/pwb/theme.rb - ActiveJSON model with inheritance support
  3. Page Part Library: app/lib/pwb/page_part_library.rb - Registry of available page parts
  4. Theme Settings Schema: app/lib/pwb/theme_settings_schema.rb - UI schema for customization
  5. CSS Variables: app/views/pwb/custom_css/_base_variables.css.erb - Core CSS custom properties
  6. Custom Liquid Tags: app/lib/pwb/liquid_tags/ - Property cards, featured properties, etc.
  7. Theme Directories: app/themes/[theme_name]/views/ - View templates per theme

Theme Resolution Flow

  1. Request comes in with subdomain (tenant identification)
  2. ApplicationController#set_theme_path determines theme from:
    • URL parameter ?theme=name (if whitelisted)
    • Website's theme_name field
    • Fallback to "default"
  3. Theme view paths are prepended (child first, then parent)
  4. 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

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

  1. Check child theme: app/themes/brisbane/views/
  2. Check parent theme: app/themes/default/views/
  3. Check application: app/views/

Page Part Resolution

  1. Check theme's custom page part template
  2. Check parent theme's template
  3. Check database-stored PagePart
  4. 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 colors
  • typography - Font families, sizes, line heights
  • layout - Container width, padding, spacing
  • header - Header style, colors
  • footer - Footer style, colors, columns
  • buttons - Button styles, sizes
  • appearance - Border radius, shadows, color scheme

Troubleshooting

Theme Not Loading

  1. Check entry exists in app/themes/config.json
  2. Verify JSON syntax is valid
  3. Restart Rails server after config changes
  4. Check: Pwb::Theme.find_by(name: 'mytheme')

Styles Not Applying

  1. Verify CSS variables are defined in :root
  2. Check body class matches theme name (.mytheme-theme)
  3. Ensure custom_styles helper is called with correct theme name
  4. Clear Rails cache: Rails.cache.clear

Page Part Not Rendering

  1. Check template exists: Pwb::PagePartLibrary.template_exists?(key)
  2. Verify Liquid syntax in template
  3. Check block_contents has data for current locale
  4. Verify page part key is in theme's supports.page_parts

Inheritance Not Working

  1. Verify parent_theme is set correctly in config.json
  2. Check parent theme exists
  3. Test: theme.parent.present?
  4. Verify view paths: theme.view_paths

Examples

Create a luxury theme extending default:

  1. Add to config.json with "parent_theme": "default"
  2. Copy only files you need to customize
  3. Create custom CSS with gold/navy palette
  4. Set custom font families (Playfair Display, Cormorant Garamond)

Add a new page part variant:

  1. Create template in app/themes/mytheme/page_parts/heroes/hero_video.liquid
  2. Add to theme's supports.page_parts in config.json
  3. Update page_parts_config with new variant

Override a specific component:

  1. Copy file from parent theme to your theme's views directory
  2. Modify as needed
  3. Child theme file automatically takes precedence

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

  1. Sharp corners (no border-radius)
  2. Gold accents on icons and dividers
  3. Subtle shadows with navy tint
  4. Hover lift effects
  5. 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/11_Theming_System.md - Full theming system documentation
  • docs/08_PagePart_System.md - Page part system details
  • app/lib/pwb/page_part_library.rb - Page part definitions
  • app/lib/pwb/theme_settings_schema.rb - Settings schema