5 things I have learned from Shopify's new theme Dawn

Dawn is the new default theme for Shopify online stores, and it was announced by Shopify at Unite. It applies all the new Online Store 2.0 features, like JSON templates, which are compatible with app blocks and sections everywhere, app extensions and it also leverages native browser features to make it an ultra fast theme.

Dawn - il nuovo tema gratuito di Shopify

As developers, we can learn a lot by reading the code base of this theme, as it was written to combine all the best practices for theme development in 2021, with a particular attention to accessibility, performance and on lean and maintainable code. In this article, I'll point out 5 things I have learned from reading the Dawn source code. To follow along with the files at hand, you can download the code from the Dawn Github repository following the instructions from our latest article.

1. You might not need JavaScript

Theme performanceis key for merchants: it directly influences conversion rates, it generates repeat business and it affects the store's ranking in the search engines. In the past few years, Shopify placed a strong focus on online stores' speed optimisation, by developing its ownOnline Speed reporting tool (visible below the published theme in every store), and by putting together a lot of documentation and resources on how to optimise existing themes.

Now that the Official Theme Store is open again, Shopify has also introduced very high standards for the themes submitted, which in order to be approved need to achieve a Lighthouse score of above 60 across the home page, product page, and collection page.

Since it represents the reference theme for new and existing theme developers, Dawn is a great example of an ultra light theme, and one of the ways it uses to optimise the performance speed is by using JavaScript only when absolutely necessary. According to the theme documentation, Dawn "leverages the latest web browsers to their fullest, while maintaining support for the older ones through progressive enhancement—not polyfills".

One example is the implementation of lazy loading, which tells the browser to load only the images immediately visible to the user and then load the other ones when they are scrolled into view. In Dawn the loading attribute is added directly to the image tags, instead of using an external JavaScript library likelazysites, which adds extra loading time to the page. As you can see from this code snippet taken from the image-width-text section, the loading=lazy attribute is set for each image directly in the HTML. The code also sets the width and the height of the image to improve accivibily and the srcset and size which will allow the user agent to select the appropriate image according to the user preferences or bandwidth conditions.

srcset="{%- if section.settings.image.width >= 165 -%}
{{ section.settings.image | img_url: '165x' }} 165w,
{%- endif -%} {%- if section.settings.image.width >= 360 -%}
{{ section.settings.image | img_url: '360x' }} 360w,
{%- endif -%} {%- if section.settings.image.width >= 535 -%}
{{ section.settings.image | img_url: '535x' }} 535w,
{%- endif -%} {%- if section.settings.image.width >= 750 -%}
{{ section.settings.image | img_url: '750x' }} 750w,
{%- endif -%} {%- if section.settings.image.width >= 1070 -%}
{{ section.settings.image | img_url: '1070x' }} 1070w,
{%- endif -%} {%- if section.settings.image.width >= 1500 -%}
{{ section.settings.image | img_url: '1500x' }} 1500w,
{%- endif -%}"
{{ section.settings.image | img_url: '1500x' }}"
sizes="(min-width: {{ settings.page_width }}px) {{ settings.page_width | minus: 100 | divided_by: 2 }}px, (min-width: 750px) calc((100vw - 130px) / 2), calc((100vw - 50px) / 2)"
alt="{{ section.settings.image.alt | escape }}"
width="{{ section.settings.image.width }}"
height="{{ section.settings.image.height }}" />

Another example of using JavaScript only when absolutely necessary is in the product page image gallery. To emphasise the product images on desktop, the UX team decided to use a two-column layout with product images on one side and product information and CTA buttons on the other, instead of the traditional product thumbnail slider. The dual scrolling effect was achieved with CSS only, through the scroll snap CSS properties, while a gallery with thumbnails with its own JavaScript logic was added on mobile only. Of course, JavaScript is used on the product page to handle variant change and other essential functionalities, such as displaying the correct variant image in the desktop image gallery of the product page.

By leveraging browser features like the loading attribute, or by choosing to implement pure CSS solutions like in the case of the slider, Shopify has created a theme that out of the box reaches a Lighthouse performance score of 96. As a guideline, Shopify gives 16KB as maximum size of the JavaScript used within a theme.

2. Use modular code

Dividing the code into multiple independent modules makes it more readable and maintainable. One of the biggest differences between Debut and Dawn is the modularisation of the .css and .js files. While in Debut, the assets folder contained a single theme.js and theme.css file, we can see that in the Dawn there are a base.css file, a global.js file and a series of component specific stylesheets and JavaScript files.

This is an update that I believe came naturally with the introduction of sections across all templates. In each section's code, you can then import only the necessary scripts and stylesheets for that specific component, without impacting the page speed or section rendering.

Continuing to follow the example of the product page, we can check out the file structure to understand how modular code was implemented in the theme. As we know, in order to implement sections everywhere, we need this to be in .json format instead of .liquid. The product page template is now a .json template that calls different sections:

  "sections": {
    "main": {
      "type": "main-product",
      "blocks": {
        "vendor": {
          "type": "text",
          "settings": {
            "text_style": "uppercase",
            "text": "{{ product.vendor }}"
        "title": {
          "type": "title"
        "subtitle": {
          "type": "text",
          "settings": {
            "text": "{{ product.metafields.descriptors.subtitle.value }}",
            "text_style": "subtitle"
        "price": {
          "type": "price"
        "variant_picker": {
          "type": "variant_picker"
        "quantity_selector": {
          "type": "quantity_selector"
        "buy_buttons": {
          "type": "buy_buttons"
        "description": {
          "type": "description"
        "share": {
          "type": "share"
      "block_order": [
    "product-recommendations": {
      "type": "product-recommendations"
  "order": [

The template includes a main-product and a product-recommendations section, which in turn include different blocks.

If we take a look at the main-product, we can see that some specific stylesheets are loaded directly in the section. There are some stylesheets and some JavaScript files. The stylesheets that were built for each component of the page such as the price, the accordion or the slider are loaded first. Then a stylesheet called component-deferred-media.css is loaded using the print media attribute, which is then changed to all on load. This is done for some stylesheets only to improve performance speed, because CSS is a render blocking resource and it blocks the page from loading. Once the page is ready, the onload attribute will change a non-functional stylesheet into a functional one. Since this "trick" causes a flash of un-styled content, it is only used for some of the stylesheets and combined with some critical css, which is the one loaded synchronously.

 {% comment %}theme-check-disable TemplateLength{% endcomment %}
{{ 'section-main-product.css' | asset_url | stylesheet_tag }}
{{ 'component-accordion.css' | asset_url | stylesheet_tag }}
{{ 'component-price.css' | asset_url | stylesheet_tag }}
{{ 'component-rte.css' | asset_url | stylesheet_tag }}
{{ 'component-slider.css' | asset_url | stylesheet_tag }}
{{ 'component-rating.css' | asset_url | stylesheet_tag }}
{{ 'component-loading-overlay.css' | asset_url | stylesheet_tag }}
 <link rel="stylesheet" href="{{ 'component-deferred-media.css' | asset_url }}" media="print" onload="this.media='all'"> 
<script src="{{'product-form.js'}}" defer="defer"> 

With regards to JavaScript, we see that there is a single file product-form.js loaded and then, lower down in the page, a javascript file to render 3D modules that is conditionally loaded if a 3D model is available. All the other logic for the product page, such as for example the one to display the correct image for each variant, is in the global.js file which is loaded in the theme.liquid file and therefore available throughout the whole theme.

As in Debut, JavaScript code can also be loaded from within the liquid section file when appropriate, and this is the case for example for the product-recommendations section. The reason for this is that the section can only be rendered after the Liquid recommendations object is populated as a response to the API call to the Product Recommendations API.

3. Make informed design choices

Shopify's UX design team that put together Dawn wrote a brilliant article about how Dawn was designed and which problems it is trying to solve. It describes the process where designers worked closely with developers to create a theme that emphasises performance but also balances the user experience for merchants and customers. Two of the choices that resonate with me as a developer are the ones regarding the fonts and the theme colours.

System fonts and library fonts

L'attenzione al miglioramento delle prestazioni e della velocità del tema non ha penalizzato l'esperienza del merchant nel personalizzare il suo negozio.

The focus on improving theme performance did not penalise user experience. The Shopify UX design team made the conscious choice to allow library fonts in parallel with system fonts, for all the elements of the page. System fonts are still the default and recommended choice, as they are the fastest fonts to load since they are already available on all customers' computers. But merchants can choose to use library fonts instead (with the limit of 2 per theme) to achieve the desired look and feel for their store. The font picker in the theme editor has also been updated with information on performance costs of selecting non-system fonts, so that the merchant has a clear idea of the impact of their fonts on the theme performance.

Theme editor font selector

Atomic color system

Similarly to their code components in Dawn, the atomic approach has been applied to the theme colours. One of the pain points for merchants throughout the years was finding a colour scheme for their themes that would work, both for aesthetics and brand identity, as well as for meeting accessibility standards.

After an audit of the existing themes on the Theme Store, the Shopify UX team settled on 7 as the perfect number of colours to use in a Shopify theme. There are three primary colours and 4 secondary colours. The core ones are the background color, text color, solid button label color, and accent colour.

Then they added an accent colour and one more background colour to provide flexibility to merchants, as well as a seventh colour for text links (outline button), which is there to allow links to stand out while still being accessible.

Atomic design system

Why is this helpful to me as a developer? It's interesting to see how the atomic approach has been applied to design decisions and how Shopify has made an opinionated theme that strives to combine performance and flexibility. Reading about the motivations behind their choices also made me reflect about the role of design systems when building themes and of best practices to keep styles unified throughout the theme.

4. Put accessibility first

Websites, and more in particular e-commerce websites, need to be accessible to all people. As a matter of fact, people with disability represent 15% of the global population, and they need to be able to navigate the store, find information about the products and successfully complete purchases. Accessibility is a very broad topic and Shopify has always placed a strong importance on this aspect of theme development. Many articles have been written on best practices for building accessible Shopify themes.

With regards to Dawn, it continues the tradition of fully accessible themes created by Shopify. Some examples from the theme are:

  • all elements have an appropriate ARIA role, so buttons have the role="button" and unordinated lists have the role="list". I have also seen the usage of the role="combobox" for the first time, in the case of predictive search in the main-search section or the search input in the header section.
  • all links have a discernible name, so for example the "previous" and "next" buttons in the mobile slider for the multicolomn.liquid section have a name attribute.
     <button type="button" class="slider-button slider-button--prev" name="previous" aria-label="{{ 'accessibility.previous_slide' | t }}">{% render 'icon-caret' %} 
  • HTML elements created through the Custom Elements API also have their accessibility attributes set from within their class. So for example, in the case of the share button, an element used in the main-article section, the accessibility attributes are set in the addAccessibilityAttributes function.
    if (!customElements.get('share-button')) {
      customElements.define('share-button', class ShareButton extends DetailsDisclosure {
        constructor() {
          this.elements = {
            shareButton: this.querySelector('button'),
            shareSummary: this.querySelector('summary'),
            closeButton: this.querySelector('.share-button__close'),
            successMessage: this.querySelector('[id^="ShareMessage"]'),
            urlInput: this.querySelector('input')
        addAccessibilityAttributes() {
          this.elements.shareSummary.setAttribute('role', 'button');
          this.elements.shareSummary.setAttribute('aria-expanded', 'false');
          this.elements.shareSummary.setAttribute('aria-controls', this.elements.shareSummary.nextElementSibling.id);

5. The power of open source

I have left the best for last. Dawn is an open source theme, which means that its source code is available for everyone to read and download on Github. This also means that everyone can contribute to the code improvements, report issues or suggest new features that can be implemented in the code, with the repository maintainers' approval.

As developers, we can not only learn from the theme code but also from the issues opened by the repository contributors and the interactions with the repository maintainers. For example, while researching for this article, I typed "product recommendations" in the issues and I learned about intersectionObserver and why it is used in this section.

Checking out the Project Board is also quite interesting, since it is displays a roadmap for the improvements and bugfixes for Dawn.

But why did Shopify decide to make Dawn an open source theme? Because it helps spread its best practices and encourage the adoption of an Open Standard, creating a virtuous circle where higher quality themes are developed and made available to merchants, who in turn decide to use Shopify as their e-commerce platform.


As of October 2021, there are 40 themes with Online Store 2.0 features in the Shopify Theme Store, and they all had to pass the quality standards outlined here - I think Shopify is succeeding in creating this new standard for high performance and beautiful themes!

Foto di Rossella Ferrandino

About the author

Rossella Ferrandino

Rossella is Nama's Front End Developer, so she translates into code your business requirements. She previously worked in events & marketing in London and moved to the tech industry as a self-taught web developer. She is also a volunteer for Women Who Code, a community aimed at empowering women in tech. Her favourite Pokémon is Snorlax.