Under the hood with Bubble’s responsive engine

Matt Neary
16 min readApr 12, 2022

A short guide to responsive design in Bubble

Watch the video version of this article

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:

  • Fixed
  • Align to parent
  • Row
  • Column

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.

Fixed width

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:

  • Icons
  • Small images
  • Small buttons
  • Upvote counter

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.

Each child has it’s own position setting
You change the position of a child element within an align-to-parent group from the inspector for the child itself

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
This product card has elements in the bottom left and top right of it’s nonant. From Etsy.

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)
How a row of groups looks at every alignment setting.

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.

The parent group here has two rows (300px each) sharing it’s vertical height, but the elements within those rows only take up a portion of the row’s height (100px).


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.

Behold! A row of columns. From Airbnb.
Marvel at this column of rows! Also from Airbnb.


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.

Element A has a right margin here.

This doesn’t just apply to siblings. A child with margin will push itself away from the border of it’s parent as well.

Margin settings on the child.

You can set the margin on any of an element’s four sides.

Here we’re setting left, top, and right margins of the central blue group.

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.

Padding settings on the 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.

Here we’re setting the padding of the outer parent group. Note that the child element here does not have a fixed width, allowing it to shrink as padding encroaches on it’s available space. More on dynamic width below.

You’ll want to use padding primarily to create whitespace (empty space) inside of a parent container.

Gap between

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.

The inefficiency of this is making me physically ill.

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)

Ahhh, so efficient! I feel better now.

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.

Row spacing.
Column spacing


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.
This group has a min width of 300px, and a max of 800px, and so it can only resize within that range. Note how the right border disappears behind the canvas once the page shrinks below the min width
  • 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:

  1. Stretch: Elements will try to make themselves as big as they can
  2. 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.

Toggling between Expand and Shrink modes. The smallest width the element can be is constrained by it’s min width, and the largest size by it’s max width.

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.

Toggling on ‘vertical stretch’.

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.

Stretch mode

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:

  1. The parent group’s max width
  2. 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.
  3. 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.

With the child group expanding it’s width as much as it can, any padding added to the parent encroaches on the child’s 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.

The parent group here has a max height of 500px, but each of her three children have min height of 300px, so their total height (600px) extends past her boundary.

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.

Here we’re setting one group to have 80% min width, and the other 20%.

Shrink mode

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
The parent here is set to ‘fit width to content’, but it can only shrink as small as the child + it’s own margin and border width, + the child’s padding.

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).

All the groups here, including the text, are fitting height to content, so as more text is added, the module gets longer.

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.

This font (Poppins) is having the bottom cut off without a min height.

Conditional behaviour

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.

The red group on the right has a conditional statement that hides (and collapses) it below a certain page width.

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.

Each of the groups here has a black colour (#000000) at 10% opacity.

That’s it!

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!

About me

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.