RFC-126: Improving cumulative layout shift for Forge apps (CLS)

As we migrate more customers and apps onto Atlassian’s platform, we’re putting a strong focus on improving both the actual and the perceived performance of all apps. A big part of that is making pages feel stable and predictable to end users as they load.

One of the most impactful levers we’ve found is setting a default viewport size for UI elements that support it. When an element has a known size before it loads, the rest of the page doesn’t jump around as much. This directly reduces cumulative layout shift (CLS) and makes the experience feel smoother for users.

What we’re asking from you:
We’re encouraging developers to start setting viewportSize (or equivalent sizing settings) in their app for any supported UI surfaces. Over time, this will become a requirement, but we want to work with you to get there in a way that respects your apps’ constraints and complexity.

In the coming months, you’ll see:

  • Warnings and lint rules to help you catch missing viewport settings early

    • e.g. “Macro <extensionKey> does not define a viewportSize.”
  • Updated guidance, examples, and best practices

  • Platform-side sizing solutions to better support dynamic and complex layouts

Specifically, we’ve identified a couple of surfaces where this problem is particularly visible today:

  • macros in Confluence, especially iframe-based macros without a defined height

  • jira:issuePanel in Jira, where panels load after the issue view and can cause noticeable shifting

Where possible, please set a default viewport size for these modules.

Where we need your help!

We understand that not all macros and UI modules can use a fixed, developer preset size. Some genuinely need to adapt to dynamic content, and we want to get an understanding of these scenarios to evolve our solution.

Here’s where we’d love your stories and ideas:

  • What kinds of dynamic content do your macros display?

  • How do you (or your users) currently handle resizing, overflow, or unpredictable content?

  • Are there technical or UX blockers you’ve run into with sizing macros in Confluence?

  • After the initial load, what would the expected experience be for your macros?

    • Would a user driven macro resizing be suitable? (e.g. interactive resize handles to allow users to determine the size of the macro content while editing a page)

    • Re-enabling dynamic sizing after a user interacts with the macro (e.g. clicks a button within the macro)

    • Developer-defined overflow behaviour (hidden/scroll/click-to-expand)

We’re committed to working with you to find solutions that improve performance. Thank you for helping us shape the future of performance at Atlassian!

4 Likes

Hi @VickyHu ,

Thank you for the RFC!

On of the features of our confluence macros is that we allow users to specify the height themselves in the macro editor:

At this point, I believe Forge does not support context parameters the way Atlassian Connect did.

In order to provide the best user experience, we would love to be able to use a Macro config property value as the viewportSize.

5 Likes

Hey,

it’s funny you should open this RFC, I was fighting with the resizing on one of our apps just yesterday :smiley:

For our macros, their size depends on the width of the screen and the content set up by the user. Mostly, for wide enough screens, we would know the dimensions of the macro at edit time or at the latest at the time the macro is first rendered.

This calculation will change for smaller screens or when the macro gets its width modified by the user.

Similar thing with issue panels, when it is loaded, a lot of the time (assuming the content doesn’t change, which it sometimes just does), we know the size the macro would need to have for next time.

What I could see help here would be a way to let an iframe (rendering on a large enough screen) that finished rendering set its current size as the initial size for the next time this iframe is loaded on a large enough screen (on smaller screens it might vary). Maybe a method in the @forge/bridge or something. Then the hosting Atlassian app could use that as the initial size for the next render and override the manifest viewportSize.

Such a mechanism would probably work for a high double-digit percentage of renders that then wouldn’t have to resize anymore or at the very least not as drastically.

Cheers,
Tobi from resolution

Edit: To specifically answer your questions:

What kinds of dynamic content do your macros display?

Differs between apps, but mostly tables, design board, PDFs, all of which may vary in size. Sometimes user resizability makes sense, sometimes not so much. The app should at least be able to give a suggestion (thinking of PDFs for example where we default to displaying a full page.

How do you (or your users) currently handle resizing, overflow, or unpredictable content?

Depends on the app. Either user-adjustable in the app config as with Remie or will auto-adjust to the content. Sometimes with scrollbars, whatever is appropriate. We try to always use 100% width (since users can define it in the Confluence editor or it is given by Jira) and we try to adhere to that and adjust the height accordingly. We try our best to avoid horizontal scrollbars.

Are there technical or UX blockers you’ve run into with sizing macros in Confluence?

Not really. It’s sometimes a bit finicky (with scrollbars and all) and you have to account for a few different cases like the user changing the width of the macro or having a small screen or resizing their browser window, but it’s fine in general.

After the initial load, what would the expected experience be for your macros?
Would a user driven macro resizing be suitable? (e.g. interactive resize handles to allow users to determine the size of the macro content while editing a page)

In some apps that would make a lot of sense, some not so much. That last category’s content can’t adjust well beyond certain size constraints that might depend on the content. Either way, our apps should be able to define these constraints and suggest an initial size after the content is known.

Re-enabling dynamic sizing after a user interacts with the macro (e.g. clicks a button within the macro)

Sure.

Developer-defined overflow behaviour (hidden/scroll/click-to-expand)

We are somewhat already offering click-to-expand in some of our apps by offering a button to open the content in a modal.

3 Likes

In one of our apps the macros display external data, either in a table or in a kind of list. The height of those (and therefore the macro height) depend on user-driven macro-config and amount of existing external data.
Example: The macro might be configured to display 10 items (paginated), but external data only has 5 items, then 5 items will be shown, which will consume less height than 10 items would. So we can’t reliably deduce from static macro-config the actual macro height during runtime. We could make best-effort guesses, but due to dynamic nature of our users’s data we can’t achieve 100% pre-determined macro-height. At least not without noticable white-space at the bottom of our macros.

Even if we could pre-determine our macro height (in pixels), mapping it to one of the available viewportSizes (according to docu: 'small', 'medium', 'large', 'xlarge' or 'max') will lose accuracy.
Question: Could a macro’s viewportSize allow pixel-values (or some other CSS length-values)?

5 Likes

I concur:

Even if we could pre-determine our macro height (in pixels), mapping it to one of the available viewportSizes (according to docu: 'small', 'medium', 'large', 'xlarge' or 'max') will lose accuracy.
Question: Could a macro’s viewportSize allow pixel-values (or some other CSS length-values)?

As pointed out by multiple replies above, currently my apps depend on both of the following for setting the size of the marcos:

  1. the iframe height in pixels (which is a user-specified value), and
  2. NOT specifying the viewportSize for the macro module in order to fallback to auto-sizing by Forge

Should viewportSize becomes mandatory and only supports small/medium/etc options, any existing user will effectively lose their configured size of the marco and will suffer visual regressions.

Therefore, the ability to specify the viewportSize in pixels will greatly benefit the users and help app developer to maintain consistency while stay compliant to the proposed changes.

2 Likes

I believe the best approach would be to combine a static default size defined in the manifest with a dynamic setHeight method from @forge/bridge.

More details:

  1. By default, app uses height defined in the manifest viewportSize, such as small, medium, large, xlarge, or max.
    For some apps, this static size is enough and no dynamic resizing is needed.

  2. Of course some apps (with modules like Confluence macros, Jira panels, JSM headers, or any module where size matters) load data first and then realize that a large list of items needs to be displayed.

  3. In such cases, the app should be able to call a function to resize its iframe, for example:

    import { view } from '@forge/bridge';
    
    view.setHeight(newHeight);
    
  4. The Forge wrapper would listen to this call and resize the apps iframe container accordingly.

  5. This way, apps can show a predictable static loader or initial layout based on the size from manifest, and then update the height dynamically when needed using a dedicated method from @forge/bridge.

What do you think?

2 Likes

From the mentioned surfaces our plugin uses the “jira:issuePanel”, where our plugin either display a list of progress bars depending on the context or a list of issues related to the current issue.
In the first case, we can guess the size after the first 2 REST calls and for the second case only after we have all the needed data. Currently we display a spinner while loading the data and only after the data is loaded the final UI will be displayed. The height still needs to be changed for cases like:

  • The user changes the configuration / append issues
  • Opening Modal layers (Datepicker etc)
  • Expand / Collapse UI Elements

For both cases a static viewportSize would not help or make if even worse as there would just be an additional resize. What would help is the idea from Tobi to be able to remember the last size from this context (and the ability from the app to read it initally to assign it to our loading spinner)

Thanks all for the thoughtful feedback and the concrete examples of how you’re handling sizing today. I’d love to play back what I’ve heard and ask a few follow‑ups.

What I’m hearing so far

  1. Pixel height is preferred over the current “small/medium/large” style sizing.
    Devs want more precise control so the iframe can better match the actual UI.

  2. You’d like height to be configurable from multiple places:

    • By dev: via manifest.yml or a runtime API/bridge, so you can set a sensible default from code.

    • By user: via a configuration UI (e.g. macro editor) so end‑users can tune height for their specific use case.

    • By platform: by remembering an app’s height after the first render and re‑using that for subsequent renders, to reduce CLS without extra work from devs.

Follow‑up questions

To help us shape this properly, I’d love your input on a few specifics:

  1. Developer vs user vs platform control

    • If you could only prioritize one of these first, which would it be, and why?

      • a) Dev-defined in the manifest/bridge

      • b) User-defined in a config/macro editor

      • c) Platform remembers height after first render

    • How should conflicts resolve? For example, if the dev sets a default and the user overrides it, is it OK that the user setting always wins? Would you want any way to “lock” sizing from the app side?

  2. For inline or byline style experiences, do you:

    • Need control over width as well as height?

    • Do you prefer to think in px width as well(e.g. 320px for a compact card)?

  3. Edit vs view mode

    • Do you currently design different layouts/heights for:

      • Confluence edit vs view mode?

      • Jira configuration vs normal view?

    • If yes, why?

Just to be sure that we are talking about the same:
We are not talking about removing the automatic resizing of forge based on the content size and instead only about the first render size? (while Forge is loading). I ask because removing the auto resize would result in a lot of work for plugin vendors.
If we are only talking about the first render, I would suggest to reflect that into the naming of the properties / functions like this

view.setInitialHeight(newHeight);
view.getInitialHeight();

or

initialViewportSize

The “setInitialHeight” function should support a context parameter (e.g. Issue, Project, Global, Macro/Gadget or the confluence counterparts). The getInitialHeight will be needed on our side until the data is loaded because otherwise the auto resize may change the size.

As for which one to priorize:

  • a) Dev-defined in the manifest/bridge
    Because this one (with context parameter) would solve most use cases
  • b) User-defined in a config/macro editor
    This one may cause backwards compatibility issues with plugins that already have this setting and wouldn’t the user have 2 seperate configs with this?
1 Like

Hey Vicky,
Since most of our macros are multi-bodied, we can’t predict what kind of content users might embed, which is ranging from native Confluence macros and third-party macros to dynamic content and long articles. So there’s no way for us to figure out the hight of the bodied macros in advance.

We suggest a new Option D, which we think might covers ~90% of use cases, where Atlassian calculates the macro content height within the Confluence page Editor mode, right before the page is published.

Pros:

  • Respects user-defined height
  • Works from the first render(no cold-start)

Cons (remains 10% of use cases):

  • Dynamic content that expands over time (e.g., nested Jira issues) may cause UI distortion, but this can be fixed by a vertical scrollable/resizable container.
  • Feasibility from your side and compatibility with Live pages editing?

Reg. the suggested options from the comments:

a) Dev-defined in manifest

  • Not really useful since it’s immutable at runtime, and we will have the cold start issue(only subsequent request will be render correctly).

b) User-defined via config/macro editor(/bridge)

  • Works for some macros, but won’t be working with nesting and bodied macros.

c) Platform remembers height after first render

This option also has a cold start problem and doesn’t account for dynamic nested content that evolves over time. Beside the stored height would need to be updated regularly.

Hi,

To answer the follow‑up questions:

  1. Developer vs user vs platform control
  • If you could only prioritize one of these first, which would it be, and why?
    • a) Dev-defined in the manifest/bridge
    • b) User-defined in a config/macro editor
    • c) Platform remembers height after first render
  • How should conflicts resolve? For example, if the dev sets a default and the user overrides it, is it OK that the user setting always wins? Would you want any way to “lock” sizing from the app side?

For us, the order should be:

  1. User-defined in config/macro editor
  2. A default Dev-defined height in the manifest/bridge for new macros & auto convert macros

With these two options available, it does not make sense for us to have the platform remember the height after first render.

The user-defined settings should always win from the dev-defined default. There is no point in letting a user define the height and then ignore their wishes. So the user defined height will always be the preferred height for us.

  1. For inline or byline style experiences, do you:
  • Need control over width as well as height?
  • Do you prefer to think in px width as well(e.g. 320px for a compact card)?

Yes, we need control over both height and width. And yes, we would always prefer pixels for both width and height.

  1. Edit vs view mode
  • Do you currently design different layouts/heights for:
    • Confluence edit vs view mode?
    • Jira configuration vs normal view?
  • If yes, why?

Our edit mode and view mode dimensions are the same, and we have no need for these to be different.

EDIT:
please note that user-defined dimensions also means that the size can change dynamically after the macro config changes. Either implemented automatically by Atlassian, or by giving app developers the ability to dynamically change the macro dimensions (as we can now)

@VickyHu what is the intention of this RFC? Is it only about having a first render size but the iframes of macros or similar modules could still resize automatically (or manually) later? Or will iframes be sized to the predefined viewportSize, however it is defined, and if more space is needed scrollbars would appear?

In case of the latter, customers of our Confluence app wouldn’t be happy about this change as we can’t predict the height for all our macros. One of them is showing data that can be changed from outside of the page editor (e.g. from a modal which is opened from a contentAction module) so that we can’t update the render size of the macro. Moreover, some of the displayed data is not visible to all users (e.g. shown content can be user provided links to other pages which are of course only displayed when you have access to them).
Another of our macros is creating output resulting from a search in Confluence. Because of different permissions, users will see different results. This macro is also interactive which means, after it is rendered users can apply filters to finetune and restart the search. Apart from the changing search results, the controls to provide the filter values also need space when they are used. There will be usability issues, if we have to squeeze it in a scrollable iframe.

Regarding your follow-up questions:

1. Developer vs user vs platform control

Priority would be option “a”. But as described above, if it means that there is no way to dynamically resize the macro after it is rendered, none of the options are suitable for us.

Resolving conflicts: not really applicable. At the moment we would not allow users to define the size to not overcomplicate macro configuration and in majority of our use cases it wouldn’t add value.

2. For inline or byline style experiences, do you

  • Control over width: having more control over the width of the Confluence content byline would be nice to have. For the view mode of block macros I’d expect that the current behavior (100% width) is kept.
  • Preference for pixel values: yes.

3. Edit vs view mode

Edit and view mode are the same.