ESC
Start typing to search documentation...
JoonWeb DEV
Storefront Design

Themes

Documentation for building and customizing themes on JoonWeb. Learn theme structure, templates, styling, and best practices for responsive design.

Recommended Starting Point

Getting Started with Theme Development

Understand the official JoonWeb theme folder structure, .jw template anatomy, global typography tokens, and JSON rules schema.

AI Section Studio & Universal Skills

Build Custom .jw Sections with AI

Use our official jw-theme-sections skill to generate responsive, error-free storefront sections in ChatGPT, Claude, Cursor, and AI coding agents.

View SKILL.md
1. Select Your AI Platform
ChatGPT Workflow: Click "1-Click Launch in ChatGPT" below to open ChatGPT with the pre-loaded prompt and skill context. Alternatively, create a Custom GPT with the system instructions below.
2. Choose a Section Example / Preset Switch presets to test different prompt types & outputs
Ready-to-Run Prompt Hero Banner
You are an Expert JoonWeb Theme Developer. Follow the official JoonWeb section specifications from:
https://joonweb.dev/skills/jw-theme-sections/SKILL.md

Generate a production-ready Hero Banner section in .jw format with:
- Top comment: {# Copy and paste this code directly into the JoonWeb Theme Editor (.jw file) #}
- Strict file order: Variables {% set %} -> <style> -> <section> -> <script> -> {% rules %}
- Scoped CSS under {{ S }} MUST be placed BEFORE <section>
- Conditionals using != '' or 'is not empty' (never != blank)
- Outer wrapper with data-bl="{{ site ? 0 : 1 }}", data-xx="1", and background{{ sc }}
- Editable blocks using data-jw-block
- Valid schema types only (radio yes/no, quickEditor, text, image_picker, range)
- Append {"type": "themes"} and {"type": "apps"} to schema blocks
- Populated presets with realistic default blocks (NEVER include themes or apps in presets)
- Right above rules schema comment: {# Do not change if you don't know #}
- No bulky or decorative divider comments
Expected .jw File Structure (Pure JW Language + Bootstrap 5.3 + Populated Presets)
{# Copy and paste this code directly into the JoonWeb Theme Editor (.jw file) #}

{% set ss = section.settings %}
{% set S  = '#jw-section-' ~ section.id %}
{% set sc = (section.colors  | default('5')) | replace({ 'background': '' }) %}
{% set bg_col   = ss.customBg == 'yes' ? ss.bgColor | default('') : '' %}
{% set text_col = ss.customText == 'yes' ? ss.textColor | default('') : '' %}
{% set card_bg  = ss.customCardBg == 'yes' ? ss.cardBgColor | default('') : '' %}
{% set card_tx  = ss.customCardText == 'yes' ? ss.cardTextColor | default('') : '' %}
{% set width_map = { "full-width": "full-width", "container": "container", "container-fluid": "container-fluid" } %}
{% set width = width_map[ss.sectionWidth] ?? 'container' %}
{% set v = ss.image is iterable ? (ss.image.src ?? ss.image.url ?? ss.image.value ?? '') : (ss.image ?? '') %}
{% set img = v != '' ? ((v starts with 'http' or v starts with '/' or v starts with 'data:') ? v : v | asset_url) : '' %}

<style>
  {{ S }} {
    padding-top: var(--pad-t);
    padding-bottom: var(--pad-b);
    position: relative;
    overflow: hidden;
  }
  {{ S }} .hb-badge {
    background: rgba(59, 130, 246, 0.12);
    color: #2563eb;
    border: 1px solid rgba(59, 130, 246, 0.25);
    font-size: 0.75rem;
    letter-spacing: 0.05em;
  }
  {{ S }} .hb-img {
    aspect-ratio: 4 / 3;
    object-fit: cover;
    transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  }
  {{ S }} .hb-media-wrap:hover .hb-img {
    transform: scale(1.03);
  }
  {{ S }}.hb-ccbg .hb-card { background: var(--ccbg) !important; }
  {{ S }}.hb-cctx .hb-card { color: var(--cctx) !important; }

  @media (max-width: 749.98px){
    {{ S }} {
      padding-top: calc(var(--pad-t) * 0.7);
      padding-bottom: calc(var(--pad-b) * 0.7);
    }
  }

  /* Builder Overrides */
  {{ S }}[data-bl="1"] .hb-img { transform: none !important; }
  @media (prefers-reduced-motion: reduce){
    {{ S }} * { animation: none !important; transition: none !important; }
  }
</style>

<section
  id="jw-section-{{ section.id }}"
  class="hb background{{ sc }} {{ card_bg != '' ? 'hb-ccbg' : '' }} {{ card_tx != '' ? 'hb-cctx' : '' }}"
  data-xx="1" data-bl="{{ site ? 0 : 1 }}"
  style="{{ bg_col != '' ? 'background:' ~ bg_col ~ ';' : '' }} {{ text_col != '' ? 'color:' ~ text_col ~ ';' : '' }} {{ card_bg != '' ? '--ccbg:' ~ card_bg ~ ';' : '' }} {{ card_tx != '' ? '--cctx:' ~ card_tx ~ ';' : '' }} --pad-t: {{ ss.pad_top | default(80) }}px; --pad-b: {{ ss.pad_bottom | default(80) }}px;">

  <div class="{{ width }}" data-id="{{ section.id }}">
    <div class="row align-items-center g-5 {{ ss.reverse_mobile == 'yes' ? 'flex-column-reverse flex-lg-row' : '' }}">
      <div class="col-12 col-lg-6">
        <div class="hb-content text-{{ ss.alignment | default('left') }}">
          {% for block in section.blocks ?? [] %}
            {% set b = block.settings %}
            {% switch block.type %}
              {% case "badge" %}
                <div class="hb-blk mb-3" data-jw-block>
                  <span class="badge rounded-pill px-3 py-2 text-uppercase fw-bold hb-badge">
                    {{ b.text | default('New Season') }}
                  </span>
                </div>

              {% case "heading" %}
                <div class="hb-blk mb-3" data-jw-block>
                  <h1 class="display-4 fw-bold mb-0 lh-tight hb-title fontStyle1">
                    {{ (b.text ?? 'Digital Storefront Innovation') | raw }}
                  </h1>
                </div>

              {% case "text" %}
                <div class="hb-blk mb-4" data-jw-block>
                  <div class="lead mb-0 hb-desc fontStyle7">
                    {{ (b.content ?? '<p>High-converting, responsive storefront architecture built natively with JW Language.</p>') | raw }}
                  </div>
                </div>

              {% case "buttons" %}
                <div class="hb-blk mb-4" data-jw-block>
                  <div class="d-flex flex-wrap gap-3 align-items-center {{ ss.alignment == 'center' ? 'justify-content-center' : '' }}">
                    {% set btn1_url = b.btn1_link is iterable ? (b.btn1_link.url ?? '') : (b.btn1_link ?? '') %}
                    {% if btn1_url != '' %}
                      <a href="{{ btn1_url }}" class="btn btn-primary px-4 py-3 rounded-pill fw-bold shadow-sm">
                        <span>{{ b.btn1_text | default('Explore Catalog') }}</span>
                        <i class="bi bi-arrow-right ms-2"></i>
                      </a>
                    {% endif %}
                    {% set btn2_url = b.btn2_link is iterable ? (b.btn2_link.url ?? '') : (b.btn2_link ?? '') %}
                    {% if btn2_url != '' %}
                      <a href="{{ btn2_url }}" class="btn btn-outline-secondary px-4 py-3 rounded-pill fw-bold">
                        <span>{{ b.btn2_text | default('Learn More') }}</span>
                      </a>
                    {% endif %}
                  </div>
                </div>

              {% default %}
                {{ render_block(block) }}
            {% endswitch %}
          {% endfor %}
        </div>
      </div>

      <div class="col-12 col-lg-6">
        <div class="hb-media-wrap position-relative">
          {% if img != '' %}
            <div class="hb-img-frame rounded-4 overflow-hidden shadow-lg">
              <img src="{{ img }}" alt="{{ ss.heading | default('Hero Banner') | striptags }}" class="img-fluid w-100 hb-img" loading="lazy" onerror="this.onerror=null;this.src='https://images.unsplash.com/photo-1441986300917-64674bd600d8?auto=format&fit=crop&w=1200&q=80';">
            </div>
          {% else %}
            <div class="hb-placeholder rounded-4 d-flex align-items-center justify-content-center text-muted p-5 bg-light">
              <i class="bi bi-image fs-1 me-2"></i>
              <span>Select an image in section settings</span>
            </div>
          {% endif %}
        </div>
      </div>
    </div>
  </div>
</section>

<script>
(function () {
  function init(sec) {
    if (sec._hbInit) return;
    sec._hbInit = true;
    var bl = sec.getAttribute('data-bl') === '1';
    if (bl) return;
  }
  function scan() { document.querySelectorAll('[data-xx="1"]').forEach(init); }
  if (window.JWHeroBanner) { window.JWHeroBanner.scan(); return; }
  window.JWHeroBanner = { scan: scan };
  if (document.readyState !== 'loading') scan();
  else document.addEventListener('DOMContentLoaded', scan);
})();
</script>

{# Do not change if you don't know #}
{% rules %}
{
  "name": "Hero Banner",
  "settings": [
    { "type": "select", "id": "sectionWidth", "label": "Container Width", "options": [
      { "value": "container", "label": "Container" },
      { "value": "container-fluid", "label": "Container Fluid" },
      { "value": "full-width", "label": "Full Width" }
    ], "default": "container" },
    { "type": "select", "id": "alignment", "label": "Alignment", "options": [
      { "value": "left", "label": "Left" },
      { "value": "center", "label": "Center" }
    ], "default": "left" },
    { "type": "radio", "id": "reverse_mobile", "label": "Reverse on Mobile", "options": [
      { "value": "yes", "label": "Yes" }, { "value": "no", "label": "No" }
    ], "default": "no" },
    { "type": "image_picker", "id": "image", "label": "Hero Image" },
    { "type": "range", "id": "pad_top", "label": "Top Spacing", "min": 20, "max": 160, "step": 5, "unit": "px", "default": 80 },
    { "type": "range", "id": "pad_bottom", "label": "Bottom Spacing", "min": 20, "max": 160, "step": 5, "unit": "px", "default": 80 },
    { "type": "header", "content": "Custom Colors" },
    { "type": "radio", "id": "customBg", "label": "Custom Background", "options": [
      { "value": "yes", "label": "On" }, { "value": "no", "label": "Off" }
    ], "default": "no" },
    { "type": "color_background", "id": "bgColor", "label": "Background Color" },
    { "type": "radio", "id": "customText", "label": "Custom Text Color", "options": [
      { "value": "yes", "label": "On" }, { "value": "no", "label": "Off" }
    ], "default": "no" },
    { "type": "color", "id": "textColor", "label": "Text Color" }
  ],
  "blocks": [
    { "type": "badge", "name": "Badge Pill", "settings": [{ "type": "text", "id": "text", "label": "Text", "default": "New Arrival" }] },
    { "type": "heading", "name": "Heading", "settings": [{ "type": "text", "id": "text", "label": "Heading", "default": "Next-Generation Digital Storefronts" }] },
    { "type": "text", "name": "Description", "settings": [{ "type": "quickEditor", "id": "content", "label": "Content", "default": "<p>Build high-converting storefronts with JW Language architecture.</p>" }] },
    { "type": "buttons", "name": "Buttons", "settings": [
      { "type": "text", "id": "btn1_text", "label": "Primary Button Text", "default": "Shop New Arrivals" },
      { "type": "url", "id": "btn1_link", "label": "Primary Button Link" }
    ] },
    { "type": "themes" },
    { "type": "apps" }
  ],
  "presets": [
    {
      "name": "Hero Banner",
      "category": "Hero",
      "settings": { "colors": "5", "alignment": "left", "pad_top": 80, "pad_bottom": 80 },
      "blocks": [
        { "type": "badge", "settings": { "text": "✨ Spring 2026 Collection" } },
        { "type": "heading", "settings": { "text": "Next-Generation Digital Storefronts" } },
        { "type": "text", "settings": { "content": "<p>Build high-converting storefronts with JW Language architecture.</p>" } },
        { "type": "buttons", "settings": { "btn1_text": "Shop New Arrivals" } }
      ]
    }
  ]
}
{% endrules %}

Documentation Categories

Select a category below to view documentation articles, API endpoints, and guides.

Core Theme Specifications & Utility Tokens

Last updated: Aug 26, 2026