Widget with multiple overlaying images

Hello, I am currently trying to build a widget.

The widget should consist of several images that are superimposed on top of each other.

Depending on the status, the images should be shown or hidden.

This is my first draft.
It looks pretty good, but unfortunately the elements can’t be hidden individually because they are nested.
If I don’t nest the blocks, the images are displayed one below the other instead of on top of each other.

All images are the same size and have a transparent background.

uid: test
tags:
props:
  parameters:
    - description: Widget Titel
      label: Widget Title
      name: title
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 12, 2025, 9:40:49 PM
component: f7-card
config:
  title: = props.title
slots:
  content:
    - component: f7-block
      config:
        style:
          margin: 0
          padding: 0
          background: url("/static/background/Layer1.png")
          background-size: 100% 100%
          aspect-ratio: 2111/2332
      slots:
        default:
        - component: f7-block
          config:
            style:
              margin: 0
              padding: 0
              background: url("/static/overlay-elemente/Layer2.png")
              background-size: 100% 100%
              aspect-ratio: 2111/2332
          slots:
            default:
            - component: f7-block
              config:
                style:
                  margin: 0
                  padding: 0
                  background: url("/static/overlay-elemente/Layer3.png")
                  background-size: 100% 100%
                  aspect-ratio: 2111/2332
                  opacity: 0
              slots:
                default:
                - component: f7-block
                  config:
                    style:
                      margin: 0
                      padding: 0
                      background: url("/static/overlay-elemente/Layer4.png")
                      background-size: 100% 100%
                      aspect-ratio: 2111/2332
                  slots:
                    default:
                    - component: f7-block
                      config:
                        style:
                          margin: 0
                          padding: 0
                          background: url("/static/overlay-elemente/Layer5.png")
                          background-size: 100% 100%
                          aspect-ratio: 2111/2332

There are several different approaches here. You can make it so the nested images don’t appear using widget expressions. You can remove the nesting by explicitly positioning your images. Or, most advanced, you can use just one block and stack all of your images in a single background-image property.

Don’t show the nested image: The code you posted doesn’t have the visible property, but I assume that is what you were using when you said that you can use some status indicator to show or not show the images (and why making one image not shown makes all it’s child images not shown as well). Instead of using the visible property, however, you can move that status test directly to the background property. Then based on that status you will set the background to the image or to 'transparent'. This is a very standard form of widget expression:

backgound: =(whatever your status test is)?('url("/static/background/Layer1.png")'):('transparent')

The expression just evaluates the test and uses the value after the ? if the test is true and the value after the : if the test is false. So in this case the true value is your image url (note the use of single and double quotes to make sure the css url function actually gets its value in double quotes), and your false value is the css color command transparent which will, of course, render nothing instead.

Don’t nest the blocks: You can build the widget so that each of your blocks are siblings (that is not nested) and still stacked on top of each other using a little bit of extra css. I would put an extra block in as the child of the card, and configure it as follows:

- component: f7-block
  config:
    style:
      margin: 0
      padding: 0
      position: relative

Then you just make each of your image blocks children of that one container block and add

position: absolute
top: 0

to each of their styles. That combination of extra styles means that each image block will ignore the positioning of any other child blocks and makes its position 0 pixels from the top of its parent. If all your image blocks are doing this, then they are all being drawn at the same point on top of each other.

Now you can just use the visible property for each block without impacting the others.

One fancy block: CSS actually supports multiple stacked images for a background out of the box. You have to use the background-image property instead of the regular background property, but then a comma-separated list of background images will be stacked (starting with the first image in the list on top). So instead of all your 5 different blocks you could just have:

- component: f7-block
  config:
    style:
      margin: 0
      padding: 0
      background-image: url("/static/background/Layer5.png"), url("/static/background/Layer4.png"), url("/static/background/Layer3.png"), url("/static/background/Layer2.png"), url("/static/background/Layer1.png")
      background-size: 100% 100%
      aspect-ratio: 2111/2332

That, of course, is not dynamic yet. To make it dynamic you need a widget expression. In this case, I’d probably go with something like this:

background-image: =[(status test 5) && 'url("/static/background/Layer5.png")', (status test 4) && 'url("/static/background/Layer4.png")', (status test 3) && 'url("/static/background/Layer3.png")', (status test 2) && 'url("/static/background/Layer2.png")', (status test 1) && 'url("/static/background/Layer1.png")'].filter( x => x).join(',')

That expression looks like a monster. It is long, but if you break it down it’s not too bad.

  1. First I put [ ... ] around the url functions (after making them strings with single quotes like we did up above). That makes them an array of strings instead of a single string.
  2. Then I put (status test) && in front of each one. The expression parser will go through the list and for each element in the list it will evaluate the (status test) if that is false it will stop there and the value of the array at that point will be false. If the status test is true then it will do the other half of the && (AND) comparison and that is just the url function as a string so that string will be the value at that point in the array. So, now you have an array which has false for any image that should not be shown and the url function string for any image that should be shown.
  3. The filter method will remove any false values from the array leaving an array of just the url function strings for the images you want to see.
  4. The join method turns the array back into a single string with the given extra string (in this case ',') in between each piece of the array. So the end result is a comma separated string of just the urls you want to see.

(Note: you didn’t give any example of your status tests. It’s quite possible that you could skip step 2 and just filter the array directly depending on what your status test is)

1 Like

Hi, first of all, thank you very much.

One fancy block:
It’s been a really long time since I worked with HTML.
I tried using multiple background images in a DIV block, but I forgot about the order. My background image covered the overlays.

For dynamic display, as known from programming: TOP->DOWN BOTTOM->UP.
First, I wanted to get the display right.

But actually, I would like to put each overlay in its own block.
Unfortunately, I can’t see any images in your example(Don’t nest the blocks).

I only see a bar with the title.

Here is my new code:

uid: test
props:
  parameters:
    - description: Widget Titel
      label: Widget Title
      name: title
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 13, 2025, 2:32:37 PM
component: f7-card
config:
  title: = props.title
slots:
  content:
    - component: f7-block
      config:
        style:
          margin: 0
          padding: 0
          position: relative
      slots:
        content:
          - component: f7-block
            config:
              style:
                position: absolute
                top: 0
                background-image: url("/static/background/Layer1")
                background-size: 100% 100%
                aspect-ratio: 2111/2332
          - component: f7-block
            config:
              style:
                position: absolute
                top: 0
                background-image: url("/static/overlay-elemente/Layer2")
                background-size: 100% 100%
                aspect-ratio: 2111/2332

A block does not have a content slot. Use default instead.

Right, my fault.

Nevertheless, it is not functioning.
Now I am able to see an image.
Unfortunately, they are now very small and again below each other.

uid: test
props:
  parameters:
    - description: Widget Titel
      label: Widget Title
      name: title
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 13, 2025, 2:32:37 PM
component: f7-card
config:
  title: = props.title
slots:
  content:
    - component: f7-block
      config:
        style:
          margin: 0
          padding: 0
          position: relative
      slots:
        default:
          - component: f7-block
            config:
              style:
                position: absolute
                top: 0
                background: url("/static/background/Layer1.png")
                background-size: 100% 100%
                aspect-ratio: 2111/2332
          - component: f7-block
            config:
              style:
                position: absolute
                top: 0
                background: url("/static/overlay-elemente/Layer2.png")
                background-size: 100% 100%
                aspect-ratio: 2111/2332

This is the downside to using this method. Once you give the element a position: absolute it is removed from the chain of size calculations so, at the moment there is nothing giving any of your elements any specific size. The only reason you see anything at all (i.e., that the blocks are not 0 x 0 in total size) is that you are using f7-blocks which have a padding value. You will have to give your image containing elements some explicit size. There are probably other styles you will have to tweak as well because you are using f7-blocks for this. I would say you would be much better off using oh-image components for this, but you have indicated that the f7-block is somehow important, although I don’t know why. If you have to have a containing element like that you are probably better off using div component instead which will have no additional styles that you do not explicitly add.