# A Strange Anomaly With Column Widths Dashboard 2.0

**URL:** <https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261>\
**Category:** Dashboard\
**Tags:** dashboard-2\
**Created:** [3 October 2025 20:53 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261 "2025-10-03T20:53:31Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![Buckskin](https://avatars.discourse-cdn.com/v4/letter/b/b9e5f3/32.png) [@Buckskin](https://discourse.nodered.org/u/Buckskin)\
**Post date:** [3 October 2025 20:53 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/1 "2025-10-03T20:53:31Z")

</div>

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?

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/1/5/15aa81f60551ad6f42eea8e8c28a0c27fa268030.png)  
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

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [3 October 2025 21:19 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/2 "2025-10-03T21:19:42Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [3 October 2025 21:26 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/3 "2025-10-03T21:26:43Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![Buckskin](https://avatars.discourse-cdn.com/v4/letter/b/b9e5f3/32.png) [@Buckskin](https://discourse.nodered.org/u/Buckskin)\
**Post date:** [3 October 2025 21:32 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/4 "2025-10-03T21:32:27Z")

</div>

The widgets are all the same

```auto

<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

```auto
    .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;

    }

```

---

<div class="post-metadata">

**Author:** ![Buckskin](https://avatars.discourse-cdn.com/v4/letter/b/b9e5f3/32.png) [@Buckskin](https://discourse.nodered.org/u/Buckskin)\
**Post date:** [3 October 2025 21:33 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/5 "2025-10-03T21:33:31Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [4 October 2025 08:02 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/6 "2025-10-04T08:02:07Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [4 October 2025 09:30 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/7 "2025-10-04T09:30:56Z")

</div>

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.

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/2/b/2bb39d76e0a7ab615a29fbe3aac882bd04f1a605.png)

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.

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [5 October 2025 08:53 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/8 "2025-10-05T08:53:32Z")

</div>

I have raised an issue for this [Widget size varies with group width · Issue #1891 · FlowFuse/node-red-dashboard · GitHub](https://github.com/FlowFuse/node-red-dashboard/issues/1891)

---

<div class="post-metadata">

**Author:** ![Buckskin](https://avatars.discourse-cdn.com/v4/letter/b/b9e5f3/32.png) [@Buckskin](https://discourse.nodered.org/u/Buckskin)\
**Post date:** [5 October 2025 13:57 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/9 "2025-10-05T13:57:16Z")

</div>

Thank you

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/1X/d073cd938eafa2e558d7c2cd59003b3ef4963033.png) [@system](https://discourse.nodered.org/u/system)\
**Post date:** [4 November 2025 13:57 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/10 "2025-11-04T13:57:54Z")

</div>

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.

---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [21 August 2026 08:35 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/11 "2026-08-21T08:35:28Z")

</div>



---

<div class="post-metadata">

**Author:** ![Colin](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/colin/32/17040_2.png) [@Colin](https://discourse.nodered.org/u/Colin)\
**Post date:** [21 August 2026 08:44 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/12 "2026-08-21T08:44:12Z")

</div>

Re-opened just to note that, in the issue linked above, it has been decided not to change the code to make the widget widths uniform, as that would slightly change the appearance of existing dashboards, which could be annoying to many users. Instead the docs have been updated to describe how to use the theme and CSS to adjust the widths accordingly. To quote from the help text from the next dashboard release:

**### Making widget widths uniform**

A group insets its widgets with a small side padding, and because that padding is shared across the group's columns, a 1×1 widget is a touch narrower in a narrow group than in a wide one. To make widths match across all group sizes, set that padding equal to `(Widget Gap − Group Gap) / 2` using a [`ui-template`](../../nodes/widgets/ui-template.md) CSS override.

For example, with the default 12px gaps, set **\*\*Group Gap\*\*** to `0` and add:

```css
.nrdb-ui-group .v-card-text {

  padding: 6px;

}

```

Here is the test flow from the issue, with the adjusted theme and CSS override applied.

```auto
[{"id":"47069383b4f76581","type":"ui-template","z":"997da33a0beedade","group":"","page":"","ui":"ID-BASE-1","name":"Adjust spacing for equal widget widths","order":0,"width":0,"height":0,"head":"","format":".nrdb-ui-group .v-card-text {\n padding: 6px;\n}","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"site:style","className":"","x":270,"y":6920,"wires":[[]]},{"id":"708fb2e61d38b271","type":"group","z":"997da33a0beedade","name":"Button Width Test","style":{"label":true},"nodes":["bafbd196632d4d9f","4698f660c113e1f3","8fdb7f4e8a8cc6c5","9e35f17b19b7fd0b","adbfdc33663e1abb","82272e556a61cd32","eb3f9d0e95d20745","e7e9e4875d859fb1"],"x":94,"y":6739,"w":832,"h":142},{"id":"bafbd196632d4d9f","type":"ui-button","z":"997da33a0beedade","g":"708fb2e61d38b271","group":"f4cd34922b93a93a","name":"","label":"button","order":5,"width":"1","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":170,"y":6780,"wires":[[]]},{"id":"4698f660c113e1f3","type":"ui-button","z":"997da33a0beedade","g":"708fb2e61d38b271","group":"f4cd34922b93a93a","name":"","label":"button","order":4,"width":"1","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":610,"y":6780,"wires":[[]]},{"id":"8fdb7f4e8a8cc6c5","type":"ui-button","z":"997da33a0beedade","g":"708fb2e61d38b271","group":"f4cd34922b93a93a","name":"","label":"button","order":2,"width":"1","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":470,"y":6780,"wires":[[]]},{"id":"9e35f17b19b7fd0b","type":"ui-button","z":"997da33a0beedade","g":"708fb2e61d38b271","group":"f4cd34922b93a93a","name":"","label":"button","order":1,"width":"1","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":330,"y":6780,"wires":[[]]},{"id":"adbfdc33663e1abb","type":"ui-button","z":"997da33a0beedade","g":"708fb2e61d38b271","group":"f4cd34922b93a93a","name":"","label":"button","order":6,"width":"1","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":730,"y":6780,"wires":[[]]},{"id":"82272e556a61cd32","type":"ui-button","z":"997da33a0beedade","g":"708fb2e61d38b271","group":"f61ab9f04574e6e5","name":"","label":"button","order":1,"width":"1","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":170,"y":6840,"wires":[[]]},{"id":"eb3f9d0e95d20745","type":"ui-button","z":"997da33a0beedade","g":"708fb2e61d38b271","group":"f4cd34922b93a93a","name":"","label":"button","order":3,"width":"1","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":850,"y":6780,"wires":[[]]},{"id":"e7e9e4875d859fb1","type":"ui-button","z":"997da33a0beedade","g":"708fb2e61d38b271","group":"f61ab9f04574e6e5","name":"","label":"button","order":2,"width":"1","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":330,"y":6840,"wires":[[]]},{"id":"f4cd34922b93a93a","type":"ui-group","name":"6x1","page":"94740f3451825304","width":"6","height":1,"order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"f61ab9f04574e6e5","type":"ui-group","name":"2x1","page":"94740f3451825304","width":"2","height":1,"order":2,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"94740f3451825304","type":"ui-page","name":"group width test","ui":"ID-BASE-1","path":"/group_width","icon":"home","layout":"grid","theme":"4c44f59af1869a6e","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":2,"className":"","visible":"true","disabled":"false"},{"id":"4c44f59af1869a6e","type":"ui-theme","name":"Equal widgets theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"density":"default","pagePadding":"12px","groupGap":"0px","groupBorderRadius":"4px","widgetGap":"12px"}},{"id":"ID-BASE-1","type":"ui-base","name":"Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-control","ui-notification"],"showPathInSidebar":false,"headerContent":"page","titleBarStyle":"default","showReconnectNotification":false,"notificationDisplayTime":"1","showDisconnectNotification":true,"allowInstall":true},{"id":"5a1af960470ee01f","type":"global-config","env":[],"modules":{"@flowfuse/node-red-dashboard":"1.30.2"}}]

```

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/1/7/177249ee1f0f94eb3c7b3f63f46d8aec39628f72.png)

---

<div class="post-metadata">

**Author:** ![Buckskin](https://avatars.discourse-cdn.com/v4/letter/b/b9e5f3/32.png) [@Buckskin](https://discourse.nodered.org/u/Buckskin)\
**Post date:** [21 August 2026 16:48 UTC](https://discourse.nodered.org/t/a-strange-anomaly-with-column-widths-dashboard-2-0/99261/13 "2026-08-21T16:48:36Z")

</div>

Cheers Colin, however

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/5/e/5e2a9778868723b4f2dd9eac8b7bf883ad1477db.png)  
As you can see the left margin is a lot less than the right. The reason is that the button widget is only 90% wide. I had to add a 6px to the left margin to get them centred (I couldn't work out how to centre the widgets using justify\* but there must be a way). I have included the template & css code just in case it helps.

```auto
<template>
    <v-card class = "btn-container">
        <v-btn class = "btn with-slider" :class = "btnClass" stacked @click="changeState()">
            {{btnLabel}}
            <v-icon class = "icon basic" :class = "iconClass" >{{iconImage}}</v-icon>
           
        </v-btn>
    
        <v-slider
            v-model = "sliderValue"
            width = "80%" 
            :min = "sliderRange.min" 
            :max = "sliderRange.max" 
            thumb-size = "12"
            :color = "sliderColour"
            @end = "updateSlider()">
        </v-slider>

    </v-card>

</template>

```

```auto
/* New CSS - now updated */
.btn-container {
    background-color: #4F4F4F; /* Very dark gray */
    border: 1px solid black; 
    border-radius: 8px; 
    width: 90%;
    height: 100%;
    margin-inline-start: 6px; /* Added to centre widget */

}

.btn {
    &.basic {
        background-color: #4F4F4F; /* Very dark gray */
        border: 1px solid black; 
        border-radius: 8px;
        width: 100%;
        height: 90%;            
    }

    &.with-slider {
        background-color: #4F4F4F; /* Very dark gray */
        border-radius: 8px;
        width: 100% !important;
        height: 70% !important;

    }

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

	}
	
	&.off {
		color: darkgrey;

	}

}

.icon {
    &.basic {
		font-size: 45px;
        height: 45px;
         width: 45px;

    }

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

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

	}
    
}

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

}

.nrdb-ui-group .v-card-text {
  padding: 6px;

}

```

After

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/5/d/5d28eadff2a97db4b3171f034c878b67d607d6d6.png)
