Under the hood with Bubble’s responsive engine
A short guide to responsive design in Bubble
A short guide to responsive design in Bubble
Bubble’s new responsive engine is really based on just three things:
Layout: The high level pattern for how elements arrange themselves
Offset: (gaps, margin, padding) Granular adjustments to the position of elements within the layout
Sizing: How much space elements should take up in the space afforded to them by layout and offset
Layout, offset, and sizing. That’s it.
They all dance together, resulting in beautiful, fluid pages. But their tight choreography can also make it hard to figure out exactly what’s going on when things aren’t behaving as expected.
We’re going to break them down one by one, but first, some definitions to get us all on the same page:
Parent: High level container (relative to children)
Child: An element inside a parent container
Siblings: One or more children
We’ll be using the language of Parent, Children, and Siblings to explain how responsiveness in Bubble works.
It’s important to note that these terms are relative; elements can adjust themselves depending on the settings of the elements around (parent) inside (children) or next to (siblings) them.
Alright — on to the show.
Every page or module you design starts with layout. Layout dictates how elements should arrange themselves within a container. It’s always set on a parent container (the first of which is the page itself), and results in all of that parent’s children positioning themselves accordingly.
You have four high level patterns:
Align to parent
Row and Column layouts are what do most of the heavy lifting, but the other two have their place as well. We’ll take them one at a time.
The only one of these layouts that lets you do true drag and drop. This is also known as absolute positioning. Wherever you drag a child element onto the page, that’s where it will stay, with a set of x and y coordinates relative to the parent.
Fixed width also let’s you layer elements on top of one another (something that isn’t possible with row or column layouts).
Fixed width can be useful for creating small atomic components, like a notification widget or avatar group, that can be nested inside of a responsive structure without the need for themselves to be responsive; since they are so small they’ll fit on any screen size so long as their positioned appropriately.
Some other examples of components where fixed width is a good choice are:
Align to parent
It works with a nonant. No, a nonant. Nonant? Yes, a nonant.
To quote directly from Bubble:
A nonant is like a quadrant, but when a rectangle is divided into 9 parts instead of 4.
Children elements can be placed in one of these 9 sections. If the parent container is a nonant, then each child can have it’s position set independently.
Some cases where this is useful are:
Centring a group inside of another group
Placing a button in the bottom right corner of a group/card
The thing to watch out for with align to parent is that depending on the screen size, elements in different sections of the nonant can begin to overlap with one another.
Alright, enough of all this nonsensical nonant talk. On to rows!
Containers with the row layout align their children along the x axis. Or, to put it another way, in a row. They’ll stack up next to one another, left to right, and any elements that can’t fit on the same line will go to the line below.
Elements in a row (or a column) cannot overlap with one another.
This is the first principle for rows: children elements will try to stay as high up in parent container as they can, provided there is room.
In the GIF above, all the elements are left-aligned, meaning they’ll move as far left on the screen as they can. This setting is managed by the parent. You can change it to be:
Centred: elements will move as close to the centre as they can
Right-aligned: elements will move as close to the right as they can
Space around: elements will position themselves so that there is equal spacing between them
Space between: the elements on the far left and right will snap to their respective edges, and the others will position themselves so there is equal spacing between them (just like space around). This can be useful for distributing elements on the far sides of a header (like a logo and user dropdown menu)
The second principle to keep in mind for rows is that when they can’t all fit together in the row, due to screen size, elements that are lowest in the hierarchy have to move to next row before groups higher in the hierarchy. This is quite intuitive.
What if you want to change the order of elements in a row? Easy — Bubble gives you the option to move an element up or down in the hierarchy, or push it straight to the first or last place.
The last piece to mention for rows is that you are able to adjust the vertical position of each element independently within a row: to the top, middle, or bottom of the row. There’s a fourth option too, which stretches the height of the element vertically to fill the row, but more on that later.
It’s important to note that a row group with a maximum height (meaning it can’t extend indefinitely, more on that below) will have its height split equally amongst each row that it needs to fit all it’s children. E.g. a 600px (pixel) parent that needs two rows will give 300px height to each.
However, the elements within each row may be shorter than their row’s height (200px for example). This can result in gaps between the elements in different rows, depending on the vertical alignment.
Then of course we have columns! Elements in a column stack themselves vertically, one on top of another.
Just like rows, you have the ability to change the order of elements in a column.
You can also change the horizonal alignment of each element independently in a column. This is similar to rows, just in the opposite direction.
A helpful way of thinking about this is that row and column layouts dictate element positions along one axis (horizontal for rows, vertical for columns) but allow you to set the position of each child element along the opposite axis.
Combining rows and columns
By combining rows and columns you can do some pretty neat things, like creating rows of columns, or columns of rows.
Offsetting simply refers to moving the position of elements by some amount. If layout gives you the high level pattern, then offset is what you’d use to make more granular adjustments.
The margin of any element is simply the space it wants between it and it’s neighbours.
This doesn’t just apply to siblings. A child with margin will push itself away from the border of it’s parent as well.
You can set the margin on any of an element’s four sides.
Margins are useful for one-off spacing adjustments between elements; usually you’ll want to use Gaps for consistency (see below).
Like margin, padding creates space between elements. Only instead of setting the gap on the outside of an element, it sets it on the inside. This means that any children elements will have their position offset by the padding setting of their parent.
It also means that only containers can set padding, since they’re the only elements that can hold children.
The exception to this is the page itself; you can’t set padding on the page. To replicate padding, use a master container as the highest element in your page hierarchy, and use max width or margin to create space between the page and this container.
Again, you can set the padding on any of the inner four sides of a container.
You’ll want to use padding primarily to create whitespace (empty space) inside of a parent container.
Look at this row group from Sony’s website:
There is a gap in between each of the groups here. Now you could do this by setting a right margin on each of the individual groups, but that’s pretty inefficient.
What if there was a way to, I dunno, just set the same sized gap in between each element in a container. And what if it was called something really intuitive, like gap between.
Thank you Bubble.
Row gap = horizontal space between each row
Column gap = vertical space between each element (only available with row layout, since elements don’t stack horizontally in a column)
You’ll want to use gap spacing as much as possible, both to keep consistency in spacing between your elements, and to make your life easier: updating one spacing property is easier than updating 4 margin properties.
So you have your layout set, and you’ve made granular adjustments with offsets. Your elements now have some space in which to reside. But how big should they be? Should they take up all the space, or as little as possible?
Sizing, when thrown in with layout and offset, can often lead to moments of “I have no idea why the page is behaving this way”. But taken on it’s own, it’s actually pretty straightforward.
Overall, the high level principles to keep in mind here are that:
Elements will change their width and height dynamically depending on various properties, like the page size and their min/max width or height settings. The exception to this is if they:
a) Have their width or height set to fixed
b) Are inside of a fixed layout container
You can set a minimum width/height and a maximum width/height for each element, which essentially gives a range within which that element can be resized.
Each element will either try to expand or shrink as much as it can. Whether or not it wants to expand or shrink is a setting you control for each element independently.
This last feature has a little bit of nuance to it, as Bubble has given us multiple controls which do the same thing. Let’s proceed one step at a time.
Understanding stretch/shrink settings
The basic premise here is that elements can be in one of two modes:
Stretch: Elements will try to make themselves as big as they can
Shrink: Elements will try to shrink as small as they can
These modes are set for both the width and height independently.
For width, we control this via the Fit width to content checkbox.
When this is unchecked, the element will be in stretch mode, and when it is checked, it will be in shrink mode. More on how these modes function below.
For height, and this is where things gets slightly confusing, we control this the same way, via a Fit height to content checkbox, but only if the parent container is a column or align to parent layout.
If the parent container is a row, we control the stretch/shrink height setting by selecting Vertical stretch as it’s vertical alignment.
Got it? Vertical stretch = Don’t fit height to content, but only in a row…
Yeah, it’s confusing. Two different buttons that do the same thing, just in different contexts.
Oh yeah, and if the parent layout is a column, you also have a Horizontal stretch option, which does exactly the same thing as leaving Fit width to content unchecked.
Why has Bubble set things up this way? It’s due to the way the underlying technology (flexbox) works — hopefully they can simplify things in future.
However, the upside is that despite all of this nuance, what these settings are actually doing is simple: they’re either telling an element to stretch as big as it can, or shrink as small as it can.
And that brings us to something important — how big, or small, can an element get?
It’s different for each mode, so let’s tackle them one at a time.
When stretching, an element will try to be as large as it can in the space afforded to it. The space afforded to it depends on a few things:
The parent group’s max width
Any offset space created by margins, padding, and gaps between. These essentially function as an extension of the space that the element cannot expand into.
Other sibling elements in the container (elements that share the same parent)
- Remember that elements in a row will try to stay as high up in the container as they can, preferably sharing the row with their siblings. If we’re in column layout, this is irrelevant, since the elements stack vertically and will never need to share horizontal space
The parent container’s max width provides the overall space that must be shared amongst the children of a container, with any offsets further restricting the available space.
How siblings share space
When it comes to other siblings in a row, there are rules governing how they should adjust their size relative to one another.
The principle we need to note here is this: elements which are in stretch mode will share the available space relative to their minimum widths/heights.
Let’s illustrate. Below we have two groups in a row container (the page) which both have a min width of 200px. Since their min widths are the same, this means they are splitting the available space equally, 50/50.
But watch what happens when we double the min width of the first of these groups to 400px.
We can see that it takes up a disproportionate part of the row’s width. That’s because it has a larger min width: 400px to the 200px of it’s sibling.
Technically what Bubble is doing here is making all siblings hit their minimum width at the same time. This is the same thing as adding their minimum widths together, which gives the smallest possible parent width where all children will still fit together in the row (600px), and then allocating space to each child according to their min widths (400px and 200px accordingly).
For column layouts, it’s similar, only when elements can’t fit in the vertical space, they just extend over the boundaries of the parent.
We can also set the minimum width as a percentage of the parent’s width, rather than in pixels.
This allows us to add multiple elements in a row/column and set their min widths/heights so that they add up to 100% (or less) and those elements will adjust their size relative to the portion they’ve been assigned.
When an element has this setting enabled, it will try to be as small as possible before hitting either (whichever is larger):
If it’s a container, the min width of children elements inside it, or the min width of it’s children’s children, or it’s children’s grandchildren, and so on
If it’s a visual element, the content inside it (e.g. the text inside of a text element)
It’s own min width plus any offsets
A common use case for shrinking is having text elements adjust their height depending on the amount of text and the font settings (font type, line spacing).
Tip: When fitting to content, make sure you set either the min width or line spacing for your text so that the button of the text isn’t cut off. This can happen on some fonts because the font’s baseline (the invisible line where the characters sit) is not exactly in line with the edge of the text element. This will need to be done for each font size you are using.
The last tool to add to your toolbelt here is the ability to adjust elements depending on the size of the page, i.e, at certain breakpoints.
You have access to the current page width operator when building your expressions, so for any element you can use it to write a conditional statement that compares the page width with one of your pre-defined breakpoints.
You can set all kinds of properties this way, like changing the size of some text, adjusting the padding of a group, or simply hiding an element altogether. Combine hiding an element with the Collapse when hidden property (and even animate it if you’re feeling frisky) to create designs that look good on any screen size.
Defining page structure
The overall thing to keep in mind when building responsive pages is to start with the high level structure first, and fill in the details later.
Just as a painter will sketch out the main lines of the image before filling in the details, create your main page architecture first and add smaller elements later. In app design we call this wireframing.
Start by defining your main parent containers and their layouts. Add the main children containers that will hold content. Add extra elements carefully and one at a time, making sure not to loose sight of how all the various elements and their settings interlock together.
A good tip here (shoutout to Greg and James from Buildcamp for this) is to create a group style with a dark background colour at 10% opacity. Every subsequent container with this style that you add inside of one of these containers will look progressively darker as the opacities add up, giving you a quick way of wireframing your main groups.
Obviously, this is mostly about the responsive engine itself, not about how you should use it. For that, the options are endless! Some great places to start learning and practicing responsive design are:
My upcoming Bubble for beginners course (coming May 2022)—you’ll have access to many video tutorials, exercises, and build walkthroughs covering responsive design
Buildcamp: Greg and James from Buildcamp have built an excellent library of tutorials covering how to create beautiful responsive modules and pages with the new engine
Atomic Fusion: A lot of top Bubblers are sharing responsive modules here. Grab some, take them apart, see how they’re built, and put them together in a new way
Play around! Try to recreate the designs of existing apps using the new responsive engine. It won’t take long for you to get the hang of it
Special shout out to design-ninja Faye Watson for giving some invaluable feedback on this guide!
I first discovered Bubble in 2017, and since then I’ve worn a few hats: developer, product manager, and educator.
Most recently I left my job as head of learning and development at Airdev (the biggest Bubble agency) to build my own Bubble course for non-technical folks, which you can check out here.
You can find some of my free Bubble tutorials on Youtube.
Also, come say hi on twitter ! You can find me as mneary0.