to select ↑↓ to navigate
iVend

iVend

Reference theme: ls_shop/themes/demo/, a working example of every case below.

1. Creating a Theme

Go to Desk → iVend Theme → New.

Set theme_name (e.g. "Demo"). Save.

This auto-scaffolds:

  • ls_shop/themes/demo/, your template files
  • ls_shop/public/themes/demo/, your static assets (css, js, images)

To activate: open the record, check is_active, save. Only one theme can be active at a time, activating one deactivates the rest.

Your theme folder structure:

themes/demo/
  pages/                ← page templates (one file per route)
  components/
    includes/           ← header, footer overrides
    core/               ← language selector, search bar, pagination, etc.
    macros/             ← item card, icon, modal overrides

2. Adding Custom Pages

Drop any .html file inside pages/ and it becomes a route automatically. No registration needed.

themes/demo/pages/about.html/en/about and /ar/about

Example: themes/demo/pages/about.html:

{% extends "templates/layout.html" %}
{% block body %}
<div class="demo-about">
  <h1>About Us</h1>
  <p>This is a custom page. No registration needed, just drop a .html file in pages/.</p>
  <p>Access this page at <strong>/{{ lang }}/about</strong>.</p>
</div>
{% endblock %}

Nested paths work too: pages/lookbook/summer.html/en/lookbook/summer


3. Overriding an Existing Component

Every component in templates/ can be overridden by placing a file at the same relative path under components/ in your theme.

ThemeFallbackLoader checks your theme first, then falls back to ls_shop defaults. No imports, no registration, just drop the file.

Example: themes/demo/components/core/language_selector.html:

<div class="demo-lang-selector" x-data>
  <a href="#" @click.prevent="window.location.href = window.location.pathname.replace(/^\/(en|ar)/, '/en')"
     class="demo-lang-btn {% if lang == 'en' %}active{% endif %}">EN</a>
  <span>|</span>
  <a href="#" @click.prevent="window.location.href = window.location.pathname.replace(/^\/(en|ar)/, '/ar')"
     class="demo-lang-btn {% if lang == 'ar' %}active{% endif %}">AR</a>
</div>

Note: Use window.location.pathname (client-side) for path switching. Do NOT use request.path, it is not available in Frappe Jinja templates.

Same pattern works for:

  • components/includes/header.html, site header
  • components/includes/footer.html, site footer
  • components/core/search_bar.html
  • components/core/pagination.html
  • components/core/sort_by.html

4. Static Assets

Put your CSS, JS, and images here:

  • ls_shop/public/themes/demo/css/
  • ls_shop/public/themes/demo/js/
  • ls_shop/public/themes/demo/images/

They are served at: /assets/ls_shop/themes/demo/css/demo.css

In templates, always use shop_theme_asset_url(), it resolves the active theme slug automatically.

Example: themes/demo/pages/index.html:

{% block head %}
<link rel="stylesheet" href="{{ shop_theme_asset_url('css/demo.css') }}">
{% endblock %}

{% block script %}
<script src="{{ shop_theme_asset_url('js/demo.js') }}"></script>
{% endblock %}

<img src="{{ shop_theme_asset_url('images/banner.jpg') }}">

After adding files to public/, run:

bench build --app ls_shop

5. Adding and Using Macros

Create a macro file in components/macros/.

Example: themes/demo/components/macros/discount_badge.html:

{% macro discount_badge(percent) %}
<span class="demo-badge">{{ percent }}% OFF</span>
{% endmacro %}

Import and call it in any theme page using the templates/macros/... path. ThemeFallbackLoader resolves it to your theme version automatically.

Example: themes/demo/pages/index.html:

{% from "templates/macros/discount_badge.html" import discount_badge %}

{% for p in products %}
  {{ p.display_name }}
  {% if p.discount_percent %}
    {{ discount_badge(p.discount_percent | int) }}
  {% endif %}
{% endfor %}

To override an existing ls_shop macro (e.g. item_card):

  • Place override at themes/demo/components/macros/item_card.html
  • Import via {% from "templates/macros/item_card.html" import item_card %}
  • Your version is used everywhere item_card is imported
Last updated 3 weeks ago
Was this helpful?
Thanks!