A Strange Anomaly With Column Widths Dashboard 2.0

I have noticed that column widths are not all the same on a page. See image below. The Group of 4 columns has wider columns than the Group having 2 columns. Does anyone have an answer as to why this is and is there a way to get round the issue?


Note: As one would expect the columns do get proportionally wider if the Browser window width is increased

Dashboard 2 v1.28
MS Edge
Default Theme
Widgets are ui-template nodes with a CSS width of 70px for button

Can you export and paste here one widget from each group please?

Also, check the open issues on the dashboard's GitHub page, I seem to remember something about ui-template's width.

The widgets are all the same


<template>
    <v-card class = "card">
        <v-btn class = "btnBasic" :class = "btnClass" stacked @click="changeState()">
            {{btnLabel}}
            <v-icon class = "iconBasic" :class = "iconClass" >{{iconImage}}</v-icon>
           
        </v-btn>

    </v-card>

</template>

<script>
    export default {
        data() {
            return {
                source: 'Dashboard',
                systemData: {button: {applianceName: 'Kitchen Air Fryer',
                                      systemAttributeID: 'OnOff'},

                            },

                // Note that when btnState = 0 the button will always be shown as 'Off'
                btnState: 0,

				btnData: {class: ['btnOff', 'btnOn'],
                          label: ['SO']
                         },
                iconData: {class: ['iconOff', 'iconOn'],
                           image: ['mdi-power-socket-uk']
                          },

                outputValue:  ['Off', 'On'],

            }
	
        },
    
        methods: {
            changeState: function () {
				this.toggleState(this.btnState)

				let topic = `${this.source}/${this.systemData.button.applianceName}/${this.systemData.button.systemAttributeID}`
                this.send( {topic, payload: this.outputValue[this.btnState]} )

            },
	
            toggleState: function () {            
                this.btnState = (++this.btnState) % this.outputValue.length
                
            },

            validateOption: function(option) {
                const optionMax = option.length - 1
                let optionSelect = this.btnState
                if (optionSelect > optionMax) {
                    optionSelect = optionMax
                }

                return option[optionSelect]

            },


            /** Update Widget property values if required
            * 
            * Input    ui-update
            *          label                Array of string, length 1 or more. Only number of outputValues
            *                               will be shown. Note: The first entry in the Array will match the first
            *                                entry in 'output'
            *          output               Array of output values (any), length 1 or more. Each 'click' of
            *                               button will output next value in Array. Button label will also
            *                               update for each 'click' if there is more than 1 element in btnData.label
            *                               Array
            *          icon                 New mdi icon 
            * 
            */
    
            setDynamicUpdates(uiUpdates) {
                for (const [property, value] of Object.entries(uiUpdates)) {
                    if (Array.isArray(value)) {    
                        if (property === 'label') {
                            this.btnData.label = value

                        }

                        if (property === 'output') {
                            this.outputValue = value

                        }

                    }

                    if (property === 'icon') {
                        this.iconData.image = value

                    }

                }

            },
			
        },

        watch:  {
            msg: function () {
                    const [protocol, applianceName, deviceName, attribute, action = ''] = this.msg.topic.split('/')
                    const btnApplianceName = this.systemData.button.applianceName

                    if (btnApplianceName === applianceName) {
                        const payloadValue = this.msg.payload?.value

                        if (attribute === 'OnOff') {
                            // Only change btnState if msg.payload is valid (i.e. is found in outputValue)
                            if (this.outputValue.indexOf(payloadValue) !== -1) {
                                this.btnState = this.outputValue.indexOf(payloadValue)

                            }

                        } 

                    }
            },

        },

        computed: {
            // Automatically compute these variables whenever VueJS deems appropriate
            btnLabel: function() {
                return this.validateOption(this.btnData.label)
    
            },

            btnClass: function() {
                return this.validateOption(this.btnData.class)
    
            },
			
            iconClass: function() {
                return this.validateOption(this.iconData.class)

            },

            iconImage: function() {
                return this.validateOption(this.iconData.image)

            },

        },

        mounted () {               
            this.$socket.on("msg-input:" + this.id, (msg) => {
                /**
                 * Comply with Dashboard 2 schema to set dynamic values
                 * 
                 * msg:     ui-update: {<property>: value}
                 * 
                */

                if ( Object.hasOwn(msg, 'ui_update') ) {
                    this.setDynamicUpdates(msg.ui_update)

                }

            })

        },

        unmounted() {
            // code here when the component is removed from the Dashboard
            // i.e. when the user navigates away from the page
            this.$socket.off(`msg-input:${this.id}`)

        },

    }

</script>

<style scoped>


</style>

CSS ui-template node

    .card {
        background-color: #4F4F4F;                          /* Very dark gray */
        border: 1px solid black; 
        border-radius: 8px; 
        width: 75px !important;
        height: 105px;

    }

    .btnBasic {
        background-color: #4F4F4F;                          /* Very dark gray */
        border-radius: 8px;
        width: 70px !important;
        height: 75px !important;

    }

	.btnOn {
        color: rgb(189, 150, 8);                            /* Dark washed rose */

	}
	
	.btnOff {
		color: darkgrey;

	}

	.iconBasic {
		font-size: 45px;
        height: 45px;
        width: 45px;

	}

	.iconOn {
        color: rgb(189, 150, 8);                            /* Dark washed rose */
        text-shadow: rgb(189, 150, 8) 0px 0px 10px;         /* Dark washed rose */

	}
	
	.iconOff {
		color: darkgrey;
		text-shadow: 0px 0px 0px;

	}

    .v-slider-thumb__surface{
        background-color: currentColor;

    }

The ui-template nodes are all the same width, (as checked with the browser developer tools) it is the columns that vary

Please supply the export as requested. That will include all the group page and theme settings so we can see if we see the same issue. Also include any relevant css templates you have

It's OK, I see what you mean. There seems to be a fundamental error in the maths somewhere. This example with four buttons (1x1) across a group that is four wide, and two buttons across a group that is two wide shows the problem.

It can be seen that the buttons in the two groups are not the same size. I suspect the problem is that in the 4x1 group there are four buttons and and 5 gaps (including the edges), whereas in the 2x1 group there are two buttons and three gaps. I think the border at the edges should only be half the width of the inter-button borders, then there would be a total of four gaps and two gaps respectively, so they will fit correctly into a group that is half the width.

I have raised an issue for this Widget size varies with group width · Issue #1891 · FlowFuse/node-red-dashboard · GitHub

Thank you