At Shopify Unite 2021, we announced theme app extensions for developers, allowing you to extend apps into merchants' themes via app blocks. Theme app extensions completely change how merchants interact with your app on their storefront.
App blocks also bring a radical change in the way apps are included inside of themes. Apps that inject inline content on a page can now extend themes using app blocks. This means that app developers can build UI components that can be added, removed, and configured through the theme editor by merchants.
Merchants can add app blocks to a compatible theme section or wrapped in a platform-provided section. These sections can be full-width, a fixed size, alongside or contain other content. This means that app blocks need to be responsive to the size of the containing section to which they're added.
Take the example below, where an app block for a customer reviews app can be added within or below the product details section. When added below, the block can take advantage of the extra space and show a star rating on the right. However, if it’s added within the product details section, there isn’t room alongside the review title for the star rating. We’d want to shift the layout so it renders below the title.
App blocks need to work in a wide array of different themes, templates, and sections, so it’s important to consider how adaptable your app block will be to these different environments.
"App blocks need to work in a wide array of different themes, templates, and sections, so it’s important to consider how adaptable your app block will be to these different environments."
In this article, we walk through best practices to make your app blocks responsive.
Browser limitations
Before we get into the solutions to making your app blocks responsive, let’s quickly look at standard solutions for responsive web design, and why they won’t work in this particular case.
Media queries
When you think of responsive web design you may think of media queries. Media queries allow us to apply CSS depending on the properties of the device and browser, the most common being viewport size. For example, you may use media queries to render a website differently on a mobile compared to a desktop.
However, in our use case, the viewport size is not what dictates the responsiveness of the UI — instead, it’s the size of the containing markup. This is because an app block will always be added inside of a theme-provided section, and is therefore unaware of its surrounding elements and content. If we use media queries, the app block may think that it has a large space to occupy when in reality it’s bunched up alongside other elements.
Note: This doesn’t mean that media queries aren’t useful in developing your app block. Media queries can still be important in adapting UI for different devices and controlling more than only the layout.
Container queries
Container queries solve these exact problems. The proposed syntax is very familiar to developers who have used media queries:
But it’s not supported in browsers (yet!).
Chrome does have this syntax implemented behind a feature flag in Chrome Canary (chrome://flags
) but it’s still in proposal. There has yet to be clear indication whether Firefox or Safari plan to adopt the syntax. This means it can’t yet be considered a standard and the syntax may change in the future.
There is growing interest in container queries, so we’ll likely see a solution before too long. However, until it’s widely supported in browsers, we need to find an interim solution.
Enter CSS grid and CSS functions
While it may not be the most elegant solution and won’t solve all the same use cases as container queries, we can achieve container-level layout responsiveness using tools that are already widely supported in browsers.
In the article Flexible Layouts Without Media Queries, Dannie Vinther walks through the components that make up a solution using CSS grid and CSS math functions. We’ll cover the general approach and how to apply it to app blocks below.
There are a few different CSS features that come together to form the solution, so let’s cover them in more detail.
min()
, max()
, and clamp()
The key part of this solution is being able to dynamically change values based on specific criteria. This is where min()
, max()
, and clamp()
can help us.
min()
As the name suggests, min()
will return the minimum value from the values passed in.
.child {
width: min(100vw, 200px);
}
In the example above, if the viewport is larger than 200px then the element will have a width of 200px, but if the viewport is less than 200px, it’ll take on the width of the viewport.
max()
max()
will return the maximum value from the values passed in.
.child {
width: max(50vw, 200px);
}
In the example above, if the viewport is larger than 400px then the element will have a width 50 percent of the viewport (since 50 percent of a viewport over 400px is greater than 200px), but if the viewport is less than 400px, it'll be 200px.
clamp()
If you want to return a value within the bounds of a maximum and a minimum, then you can combine both of the above functions. Alternatively, there is a convenience function to achieve this called clamp()
, which takes the minimum value, preferred value, and maximum value.
.child {
width: max(50vw, min(200px, 100vw)); // equivalent to clamp() below
width: clamp(50vw, 200px, 100vw);
}
In the example above, the width will never be smaller than 200px.
Units
These CSS functions are straightforward enough, but in the examples above we were using vw
units, which are equivalent to the viewport width. This is solving a similar problem that media queries already help with. For our app block use cases, we’ll want to calculate relative to the parent element. Luckily, we can do that by using the percentage unit.
.child {
width: clamp(50%, 200px, 100%);
}
CSS grid
The above CSS Functions allow us to get specific values relative to the parent container. But how do we go about using these to lay out our app blocks? One way is with CSS grids.
CSS grids have become a popular way to lay out elements on a web page, simplifying many common use cases. In the past, we’ve used tables, floats, inline-blocks, and more recently, Flexbox to help lay out web pages. And while Flexbox still has its use cases, CSS grid has the advantage of being a two-dimensional grid-based layout system, which means greater control for developers.
For an in-depth look at the features of CSS grid, we recommend checking out A Complete Guide to Grid on CSS-Tricks. For this post, the important aspect to consider is that a grid is made up of two parts: the grid container and the grid item.
-
Grid container: The wrapping element which has
display: grid
applied - Grid item: The direct child elements of the grid container
For the sake of this post, we’ll be mostly concerned with the properties and values that can be applied to the grid container. Let’s look at an example to understand how we’d use CSS functions to control the CSS grid layout.
We can see the clamp CSS function at play here, but there are a lot of other CSS grid-specific aspects that can make it overwhelming. Let's tackle each of these separately:
-
display: grid
: This tells the element that we want to use CSS grids to lay out this element and its children -
grid-template-columns
: This defines the sizing for the grid columns -
repeat
: Allows for repeated size values forgrid-template-columns
andgrid-template-rows
properties. For example, you may want the first N to be one size and the last one to be a different size.auto-fit
tells the browser to handle the column sizing and element wrapping for us, so that the elements will wrap into rows when the width is not large enough to fit them in without any overflow. -
minmax
: This takes a min value and a max value. It then defines a size range greater than or equal to min and less than or equal to max. It’s only available within CSS grids.1fr
above tells the browser to distribute the space between the columns so that each column equally gets one fraction of that space.
For a more in-depth look at auto-sizing in CSS grid, check out Sara Soueidan’s article Auto-Sizing Columns in CSS Grid: auto-fill
vs auto-fit
.
Putting all this together, we’re telling the browser to use CSS grid to layout the element’s items and use auto-fit
alongside a minmax()
to let the browser figure out based on the clamp()
calculation when it is appropriate to “break” the column count and create a dynamically responsive grid.
Limitations
Of course, if CSS grid and CSS functions solved all the responsive container problems, then we wouldn’t need container queries, but that’s not the case. This approach comes with some limitations on what you can change based on the parent container's size.
Specifically, this solution:
- Doesn’t allow showing or hiding of specific elements
- Doesn’t allow modifying non-size based properties
Despite these limitations, with CSS grid and CSS functions, we can ensure that app blocks are dynamic enough to look great wherever they’re added on a page.
Using CSS to build responsive app blocks
Theme app extensions and app blocks bring a lot of exciting opportunities. With them, the merchant has full control over where the app block is added to their storefront. That means it’s up to developers to anticipate how merchants will want to use app blocks, and ensure that they look good and work well. Until container queries are better-supported, CSS grids and CSS functions provide a decent interim solution.
Read more
- How to Build a Shopify App: The Complete Guide
- Introducing Online Store 2.0: What it Means For Developers
- How to Level Up Your App with Theme App Extensions
- How to Use Polaris to Build an App UI in HTML or React
- Everything You Need to Know About Development Stores
- An Overview of Liquid: Shopify's Templating Language
- How to work with Metafields when building Shopify themes
- How to Build a Customizable Related Products Section
- 5 Community-Built API Tools Developers Should Check Out
- Three Types of Trending Apps in 2017 and What You Can Learn for 2018