Getting Started
1 Endpoints / GuidesJoonWeb Theme development introduction and folder structure
Documentation for building and customizing themes on JoonWeb. Learn theme structure, templates, styling, and best practices for responsive design.
Understand the official JoonWeb theme folder structure, .jw template anatomy, global typography tokens, and JSON rules schema.
Use our official jw-theme-sections skill to generate responsive, error-free storefront sections in ChatGPT, Claude, Cursor, and AI coding agents.
Run in your theme project root to download the complete skill so @jw-theme works instantly
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
{# 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 %}
If you prefer creating a rule file manually instead of running the 1-click terminal install command, save this snippet into .cursorrules in your project root:
# JoonWeb Theme Developer Rules
When generating or modifying .jw sections:
1. Reference Skill: https://joonweb.dev/skills/jw-theme-sections/SKILL.md
2. Framework: Bootstrap 5.3 + Bootstrap Icons + jQuery (Never Tailwind CSS).
3. Syntax: Pure JW Language ({% if %}, {% elseif %}, {% else %}, {% endif %}, {% for %}).
4. Scoped CSS: <style> targeting {{ S }} placed BEFORE <section>.
5. Schema: Always provide valid JSON {% rules %} ... {% endrules %} with fully populated presets.
6. Auto-Suggest: Search endpoints must integrate debounced /suggestions.json?q={query}&limit=8.
Select a category below to view documentation articles, API endpoints, and guides.
JoonWeb Theme development introduction and folder structure
Typography and background color utility classes
Section anatomy, jw_attributes, render_block, and custom block definitions
Ready-to-use production section templates
Step-by-step guide to converting static HTML into dynamic .jw sections
Build custom JoonWeb (.jw) sections and layouts using conversational AI models, ChatGPT, Claude, and Cursor.