Storefronts V2
Sunrise is Swell's official theme for Proxima. It's built as a Shopify theme in order to showcase Proxima's support for such themes, and to demonstrate how to add support for Swell's native subscriptions in such themes. This guide outlines Sunrise's key features and theme sections.
Sunrise is in active development and open-source on Github. Contributions are welcome.
As with all storefront themes, Sunrise offers a fully visual editing experience in the Swell Theme Editor, leveraging the configuration of its storefront app, Proxima.
Sunrise comes with over 15 sections that can be used in creative ways throughout a storefront. Each section has a number of blocks that can be customized with settings, and reordered or hidden as a merchant is developing their online store.
Native support for subscriptions are a key part of what makes Sunrise a good starting point for any merchant, and as a base for developing new themes. While the majority of the theme follows Shopify standards, its implementation leverages Swell's built-in subscription support.
Here we'll describe each content section that ships with Sunrise, showcasing the theme's out of the box flexibility and providing a solid foundation for custom sections.
An announcement bar with a single announcement or a slideshow of announcements. Can be displayed on the home page only if used in a header section group.
A series of rows with collapsible content, often used in a FAQ section. Each row block has customizable heading and content configurations.
A list of feature items, typically with icons, headings, and descriptions. Ideal for highlighting product benefits or services in a grid or column layout.
A section showcasing a number of products from a category, showing product names, images and prices. Allows linking to the category page for targeted promotions.
A versatile block for prominent content like images, text, and buttons. Used to feature articles, testimonials, or calls to action with customizable layouts.
A full-width banner section with large image, overlay text, and buttons. Commonly placed at the top of pages for impactful introductions or promotions.
A form for customers to subscribe to your email newsletter, including input fields, submit button, and success messages.
A flexible text editor section supporting headings, paragraphs, lists, and links. Perfect for adding detailed content like about pages or blog excerpts.
A rotating carousel of images or slides with captions, buttons, and navigation controls. Supports autoplay and timing adjustments for dynamic displays.
A two-column layout for side-by-side content, such as image and text pairs. Each panel can include media, headings, and buttons for balanced presentations.
A full-width banner with looping video background, overlay text, and calls to action. Enhances visual engagement on home or landing pages.
An embedded video player from platforms like YouTube or Vimeo. Includes options for autoplay, controls, and aspect ratio to integrate multimedia content.
When making changes to Sunrise code, you'll use the Swell CLI to simultaneously bundle JavaScript and CSS, and push changes to a Swell store.
First, download the theme from your Swell store. This command assumes you have Sunrise installed in at least one storefront.
swell theme pull sunrise
Once downloaded, navigate to the directory and install NPM dependencies.
cd sunrise/
npm install
In the course of development, you'll run the following command in the top-level directory of the theme app.
swell theme dev --bundle
Behind the scenes, the CLI --bundle flag calls the npm run bundle script configured by package.json when any files are modified.
"scripts": {
"watch": "swell theme dev --bundle",
"watch:local": "swell theme dev --bundle --local",
"bundle": "concurrently -k -p none \"npm run bundle:css\" \"npm run bundle:js\"",
"bundle:css": "npx tailwindcss -i ./src/css/main.css -o ./theme/assets/main.css --watch --minify",
"bundle:js": "rollup -c -w"
}
A shortcut npm run watch script is also configured here, which includes bundling by default.
Sunrise uses Rollup to bundle and optimize JavaScript.
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import terser from '@rollup/plugin-terser';
export default {
input: 'src/js/main.js',
output: [
{
file: 'theme/assets/bundle.js',
format: 'esm',
sourcemap: false,
},
],
plugins: [
resolve(),
commonjs(),
terser({
/* compress: {
drop_debugger: false, // Optional: Preserve debugger statements
}, */
}),
],
};
This configuration tells Rollup that source files are imported to src/js/main.js and then combined to ESM format in theme/assets/bundle.js, which is then loaded via script tag in theme/layout.liquid.
Custom HTML elements are the preferred method of connecting JS logic with components in theme sections. This approach simplifies attaching events an encapsulating logic, following the well-documented Web Components standard.
Here's an example of how the <cart-count> element in Sunrise subscribes to a global event to update itself when an item is added to the cart by another element.
import eventBus from "./utils/event-bus";
export class CartCount extends HTMLElement {
constructor() {
super();
this.unsubscribeItemCountUpdate = null;
this.onUpdateItemCountBound = this.onUpdateItemCount.bind(this);
}
connectedCallback() {
this.unsubscribeItemCountUpdate = eventBus.on(
"cart-item-count-update",
this.onUpdateItemCountBound
);
}
disconnectedCallback() {
if (this.unsubscribeItemCountUpdate) {
this.unsubscribeItemCountUpdate();
}
}
onUpdateItemCount(itemCount) {
this.textContent = itemCount;
}
}
<cart-count>{{ cart.item_count }}</cart-count>
Sunrise uses Tailwind to bundle and optimize CSS, and to apply most styles via classes throughout theme sections and snippets.
Tailwind is configured in tailwind.config.js. Several Tailwind utilities and components are imported through src/css/main.css, which also defines a few global styles.
Sunrise supports both standard purchase options, and subscription purchase options at the same time, depending on how the product is configured. This sections describes how the implementation works in Sunrise specifically, with an overview of the objects and properties involved.
The main product page section, sections/product.liquid, renders several blocks including buy_buttons.
...
{% for block in section.blocks %}
...
{% case block.type %}
...
{% when 'buy_buttons' %}
{% render 'product-buy-buttons',
product: product,
block: block,
color_scheme: color_scheme,
variants_color_scheme: variants_color_scheme
%}
...
{% endcase %}
{% endfor %}
...
This block passes rendering to snippets/product-buy-buttons.liquid for the purpose of combining purchase option choices with other buy-button functionality.
<div class="flex flex-col gap-24">
{% render 'product-purchase-options',
product: product,
block: block,
variants_color_scheme: variants_color_scheme
%}
...
</div>
This snippet then renders snippets/product-purchase-options.liquid which contains the logic for displaying either or both standard and subscription purchase options.
{%- if purchase_options.subscription -%}
<product-purchase-options
id="product-purchase-options"
hx-swap-oob="true"
>
{%-if purchase_options.standard -%}
<div class="flex flex-col gap-6">
{% render 'product-purchase-option-standard',
product: product,
block: block,
purchase_option: purchase_options.standard,
variants_color_scheme: variants_color_scheme
%}
{% render 'product-purchase-option-subscription',
product: product,
block: block,
purchase_option: purchase_options.subscription,
variants_color_scheme: variants_color_scheme
%}
</div>
{%- else -%}
{% render 'product-purchase-option-subscription',
product: product,
block: block,
single_option: true,
purchase_option: purchase_options.subscription,
variants_color_scheme: variants_color_scheme
%}
{%- endif -%}
</product-purchase-options>
{%- endif -%}
Finally, let's look at the full implementation of the subscription purchase option. All of these objects and properties are native to Swell. Given there are many ways to configure subscription products, the snippet handles displaying the option description and pricing elements accordingly.
{%- liquid
assign selected_plan = purchase_option.plans | find: 'selected', true
assign multiple_plans = purchase_option.plans.size > 1
assign layout = block.settings.subscription_layout
assign show_dropdown = layout == 'dropdown' and multiple_plans
assign show_title = single_option or multiple_plans
assign show_border = show_dropdown or single_option != true and multiple_plans
-%}
<product-purchase-option-subscription
class="
flex
flex-col
gap-8
{% if show_border %}
p-12
rounded
border
{% endif %}
{% if show_dropdown %}
{% if purchase_option.selected %}
btn-primary-{{ variants_color_scheme }}
{% else %}
btn-secondary-{{ variants_color_scheme }}
{% endif %}
{% else %}
{% if purchase_option.selected %}
border-accent-{{ variants_color_scheme }}
{% else %}
border-secondary-{{ variants_color_scheme }}
{% endif %}
{% endif %}
"
{% if purchase_option.selected %}
selected
{% endif %}
>
{% if show_title %}
<div class="flex items-center justify-between gap-6">
<span
class="
{% if show_border %}
text-title-small
text-{{ variants_color_scheme }}
{% else %}
text-body-medium
text-secondary-{{ variants_color_scheme }}
{% endif %}
"
>
{{ 'sections.product.purchase.subscription' | t }}
</span>
{%- if selected_plan and show_border -%}
<div class="flex items-center">
<span class="text-body-large font-medium text-{{ variants_color_scheme }}">
{{- selected_plan.price | money -}}
</span>
<span class="lowercase text-body-small text-{{ variants_color_scheme }}">
{%- render 'subscription-plan-schedule',
schedule: selected_plan.billing_schedule,
prefix: "/"
-%}
</span>
</div>
{%- endif -%}
</div>
{% endif %}
{% if show_dropdown %}
<dropdown-menu class="relative flex flex-col group text-title-medium text-{{ variants_color_scheme }}">
<dropdown-trigger
class="
flex
items-center
justify-between
px-14
py-10
gap-8
rounded
group-aria-expanded:rounded-b-0
btn-secondary-{{ variants_color_scheme }}
"
>
<div class="flex items-center flex-wrap">
{%- if selected_plan -%}
{%- render 'subscription-plan-schedule',
schedule: selected_plan.billing_schedule
-%}
{%- if selected_plan.order_schedule -%}
,
{% assign delivered = 'sections.product.purchase.subscription_delivered' | t %}
{% assign prefix = "" | append: delivered | append: " " %}
<span class="lowercase">
{% render 'subscription-plan-schedule',
schedule: selected_plan.order_schedule,
prefix: prefix
%}
</span>
{%- endif -%}
{%- else -%}
<span class="text-title-small text-secondary-{{ variants_color_scheme }}">
{{ 'sections.product.purchase.subscription' | t }}
</span>
{%- endif -%}
</div>
<ion-icon name="chevron-down-outline" class="size-18 text-secondary-{{ variants_color_scheme }}"></ion-icon>
</dropdown-trigger>
<dropdown-options
class="
absolute
top-full
w-full
hidden
group-aria-expanded:flex
flex-col
rounded
group-aria-expanded:rounded-t-0
border
border-color-{{ variants_color_scheme }}
group-aria-expanded:border-t-0
bg-{{ variants_color_scheme }}
py-6
px-4
gap-4
z-10
"
>
{%- for plan in purchase_option.plans -%}
<dropdown-option
class="
flex
items-center
justify-between
py-6
px-10
w-full
rounded
cursor-pointer
"
value="{{ plan.id }}"
{% if plan.id == selected_plan.id %}
selected
{% endif %}
>
<div class="flex items-center flex-wrap">
{%- render 'subscription-plan-schedule',
schedule: plan.billing_schedule
-%}
{%- if plan.order_schedule -%}
,
{% assign delivered = 'sections.product.purchase.subscription_delivered' | t %}
{% assign prefix = "" | append: delivered | append: " " %}
<span class="lowercase">
{% render 'subscription-plan-schedule',
schedule: plan.order_schedule,
prefix: prefix
%}
</span>
{%- endif -%}
</div>
{% if plan.id == selected_plan.id %}
<ion-icon name="checkmark-outline" class="size-18 text-{{ variants_color_scheme }}"></ion-icon>
{% endif %}
</dropdown-option>
{%- endfor -%}
</dropdown-options>
</dropdown-menu>
{%- if selected_plan.billing_schedule.trial_days > 0 or selected_plan.description -%}
<div class="flex flex-col gap-4">
{%- if selected_plan.billing_schedule.trial_days > 0 -%}
<span class="text-body-medium text-secondary-{{ variants_color_scheme }}">
{{ 'sections.product.purchase.includes_trial' | t: trial_days: selected_plan.billing_schedule.trial_days }}
</span>
{%- endif -%}
{%- if selected_plan.description -%}
<span class="text-body-medium text-{{ variants_color_scheme }}">
{{ selected_plan.description }}
</span>
{%- endif -%}
</div>
{%- endif -%}
{% else %}
<radio-button class="flex flex-col gap-6">
{%- for plan in purchase_option.plans -%}
<radio-option
class="
flex
justify-between
p-12
rounded
{% if plan.id == selected_plan.id %}
btn-primary-{{ variants_color_scheme }}
{% else %}
btn-secondary-{{ variants_color_scheme }}
{% endif %}
"
value="{{ plan.id }}"
{% if plan.id == selected_plan.id %}
selected
{% endif %}
>
<div class="flex flex-col justify-center">
<span class="text-title-small">
{% if show_title %}
{%- render 'subscription-plan-schedule',
schedule: plan.billing_schedule
-%}
{% else %}
{{ 'sections.product.purchase.subscription' | t }}
{% endif %}
</span>
{%- if plan.order_schedule or plan.description -%}
<div class="flex flex-col gap-4 mt-8">
{%- if plan.order_schedule -%}
<span class="text-body-small text-secondary-{{ variants_color_scheme }}">
{{ 'sections.product.purchase.subscription_delivered' | t }}
<span class="lowercase">
{% render 'subscription-plan-schedule',
schedule: plan.order_schedule
%}
</span>
</span>
{%- endif -%}
{%- if plan.description -%}
<span class="text-body-small text-secondary-{{ variants_color_scheme }}">
{{ plan.description }}
</span>
{%- endif -%}
</div>
{%- endif -%}
</div>
<div class="flex flex-col items-end gap-4">
<div class="flex items-center whitespace-nowrap">
<span class="text-title-medium">
{{- plan.price | money -}}
</span>
{%- unless show_title -%}
<span class="lowercase text-body-small">
{%- render 'subscription-plan-schedule',
schedule: plan.billing_schedule,
prefix: "/"
-%}
</span>
{%- endunless -%}
</div>
{%- if plan.billing_schedule.trial_days > 0 -%}
<span class="ml-auto text-body-small text-secondary-{{ variants_color_scheme }}">
{{ 'sections.product.purchase.includes_trial' | t: trial_days: plan.billing_schedule.trial_days }}
</span>
{%- endif -%}
</div>
</radio-option>
{%- endfor -%}
</radio-button>
{% endif %}
</product-purchase-option-subscription>
If you're starting with a theme that was originally made for Shopify, we welcome you to learn from and copy logic from Sunrise in order to adapt any theme with subscription support. There are many ways to structure a theme's code, however the following templates are entry points to start with.
- templates/product.json – References one or more sections to display products and their relative purchase options.
- templates/customer/subscription.json – The customer account page for displaying subscription details, potentially adding the ability to pause or cancel subscriptions. You may also want to add the ability to update billing information using one of the Payment elements available depending on your payment gateway of choice.
- Clone Sunrise on Github.
- Learn more about Swell Themes.
- Take a look at Proxima, the official storefront app for Sunrise.