How to reverse a repeating group (for chat scenarios and more)

The repeating group is, for now, only top fed. This means that the list defaults to showing content at the top of the list first. Adding a new entry to the list and being able to see it happens at the top. You have to scroll to see the content at the bottom of the list.

This makes it hard to enable chat scenarios which , for a variety of reasons, strongly prefer bottom fed lists.

I found a pretty light weight way to do this. I havent done extensive testing across platforms & browsers yet so use with caution.

You’ll need the following free controls

  1. Classify to assign classes to elements
  2. Toolbox to run a little javascript

To do this we’ll need to
A. Flip the repeating group vertically (which flips the content and the scrolling)
B. Unflip the content within the repeating groups
C. Un-reverse the scrolling

A. Flipping the repeating group

  1. Make sure element ID attribute is exposed. You do this in settings~general.


  1. Add a html control somewhere on the page and add the following text inside the control
.mirror {
      transform: scaleY(-1);
  1. In the properties of your repeating group add the following in the ID field.

messageList{addClass: “mirror”}

If you test it now you should see the repeating group is flipped and the top of the list will be at the bottom. But … everything inside the list will be upside down. You’ll also notice that scrolling is backwards within the repeating group.

B. Unflipping the content of the RG

Unflipping the content within the repeating group is just as easy.

  1. Simply reference the mirror class we added to the html earlier.

{addClass: “mirror”}

Now if you check your RG again you’ll see that the content is no longer upside down. But … scrolling is still backwards

C. Unreversing the scrolling

You’ll need a little bit of javascript for this.
On the page load event create a “run javascript” workflow. Add the following javascript.

function chSwitchScroll(event)
chScrollPosition = chScrollPosition - event.deltaY

if ( chScrollPosition < 0) chScrollPosition = 0

if ( chScrollPosition > msglist.scrollHeight) chScrollPosition = msglist.scrollHeight

msglist.scroll(0,chScrollPosition, {behaviour:“smooth”})



var chScrollPosition = 0
msglist = document.getElementById(“messageList”)

And thats it.

Combined with the new responsive engine that bubble is beta’ing (which is actually pretty good) its now very possible to make a chat feature that works as you’d expect.


This really should be something Bubble does natively @allenyang, @cal… provided as a toggle for “load from top” vs " load from bottom."


@cal To be clear, Bubble should natively build this because the current native solution - to trigger a workflow to scroll to the bottom of the repeating group - completely voids the performance gains of a regular repeating group’s lazy loading. And known plugins for this continue to have (unfixable) issues (Inverted Repeating Group,
Reverse Repeating Group).


@PineappleJoe thanks for sharing this!

I’ve followed exactly, but the javascript is producing errors.

Is this working for you currently in your own app(s)?

What errors?

The plugin Toolbox / action Run javascript threw the following error: SyntaxError: Invalid or unexpected token
at eval (PLUGIN_1488796042609x768734193128308700/Toolbox-action–Run-javascript-.js:3:63)

Sounds like a copy paste problem. Can you screenshot what you entered in the JavaScript edit box?

Great tutorial!

I currently use the plugin List Shifter to do this. Easy to use, lightweight and also lets you do alot more with lists.


So I tried to copy the code verbatim and it didn’t work for me either. I get an the unexpected token error as well. After inspecting the code line by line … its identical to my working code. I think the copy/paste from bubble is introducing some hidden character that cant be processed or something like that.

Here is a minified version which should remove any opportunity for hidden chars to be added. I also wont format it in the code block to keep it as simple as possible.
function chSwitchScroll(l){(chScrollPosition-=l.deltaY)<0&&(chScrollPosition=0),chScrollPosition>msglist.scrollHeight&&(chScrollPosition=msglist.scrollHeight),msglist.scroll(0,chScrollPosition,{behaviour:"smooth"}),l.stopPropagation(),l.preventDefault()}var chScrollPosition=0;msglist=document.getElementById("messageList"),msglist.addEventListener("wheel",chSwitchScroll,!1);

Let me know if it works for you.

Edit: After more testing … I have 100% confirmation that a hidden char is being added. Even with no formatting. So the preformatted text block is the only one that actually seems to work. I can copy/paste the code above and it works as expected.

How bizarre. I actually managed to get it to work by fiddling with the JS myself yesterday, but could no figure out what I’d done! So that explains it.

This solution works pretty well with the mouse scroll wheel, albeit not as responsive as natively, which makes sense since JS is executing the scroll.

But with my laptop trackpad there is a massive delay between the swipe action and the scroll behaviour. Are you seeing the same thing?

Really appreciate you digging in to this and sharing!

@ihsanzainal84 what’s your use case?
List shifter works great for this if the list is relatively short (< 500). The problem is that it has to fetch all entries before outputting them, so for long lists there’s an inevitable loading delay.

The above method with JS preserves Bubble’s lazy loading, which only fetches entries as they are scrolled into view.

@nickc FYI - being able to reverse the direction of a RG, while preserving lazy loading, would be a fantastic feature. Lots of hacky workarounds currently for getting this behaviour but all have shortcomings!

I currently use LS’s reverse function in my app’s chat list but I designed it to be a relatively small list with the latest 30 messages when it first loads. It will only load more on user input (clicks on “View More”).

What you can try is to load, for example, the latest 50 messages on the element load (or page load) and once those are loaded, load the rest.

Ahh yes, great thinking!

I experience no delay at all. Mouse wheel, track pad, mobile screen all work the same as regular scrolling for me. Across browsers (chrome, firefox & safari).

Im on a mac. Are you on windows?


Yeah I’m on Windows.

@PineappleJoe looks like the delay was due to a css scroll-behavior: smooth; that I inadvertently left on the page.

So it’s working now without the delay!

The only thing preventing it from being perfect is the non-smooth scrolling:

At first I thought this was due to a typo in the JS (behaviour:"smooth" instead of behavior:"smooth") but changing that didn’t make a difference.

Is your scroll behaviour smooth on your machine?

Note: It’s smooth with a trackpad. It’s the mousewheel that’s the problem.

Good catch on the name of the property. On my Mac the scrolling is perfectly smooth. If I use my mouse which can either have a detent scroll or smooth scrolling, when I use detent scrolling it chunks along but thats how it works on every other scrollable page in the browser. When the mouse is set to smooth scrolling it works perfectly. I should try it on windows when I get a chance and see if its rough on windows for me too.

How do you manage the scrolling?
What did you reverse with the List Shifter? The way I have done is like the sorting with created date.


Nothing of this works for me…
How is it possible?

I have installed the plugins of course… and ID Attribute is exposed, I use it a lot
Is stuck at the first phase

This doesn’t work at all: messageList{addClass: “mirror”}

How do you guys manage it to work?


I ran into the same problem.
I got it to work by rewriting the double quotes (").

1 Like


Could you help me with the last Javascript part?

  1. If I paste in the text from the original post I get the following error:

Bug in custom code SyntaxError: Invalid or unexpected token
at eval (PLUGIN_1488796042609x768734193128308700/Toolbox-action–Run-javascript-.js:3:395)

  1. If I paste in the text from the “minified version” in the reply to MattN, I get this error:

Bug in custom code TypeError: Cannot read properties of null (reading ‘addEventListener’)
at eval (eval at (PLUGIN_1488796042609x768734193128308700/Toolbox-action–Run-javascript-.js), :1:333)
at eval (PLUGIN_1488796042609x768734193128308700/Toolbox-action–Run-javascript-.js:3:390)