Customizing appearance of UI-Switch node

I'm interested in modifying the standard ui-switch node to resemble the one from this image here (ignore the image on the right. I'm only interested in the app screenshot):

The main things I want to change are:

  • Making the switch node larger (I sort-of got this working using the transform method with CSS)
  • Making the track for the switch the same size as the thumb (just like in the image above)
  • Adding an On/Off label inside the switch itself like the image above
  • Controlling the color of the thumb knob on the switch

Can this all be done with CSS or would I need to make a custom control somehow with a template node? If so, what would be a good approach for this? Are there any custom widgets out there that could do this?

Thank you,
Jason O

It is CSS thing and only. If element count is same as the original switch has, there is no need to make it from scratch with ui_template node.
Here's my discreet example:
image

2 Likes

Wow that is really nice looking! So far, I have been playing with the CSS on the page and figured out how to make the slider bar behind the thumb not taller to match the height of the switch. But how to do you add the text labels? I was also trying to make the switch a bit longer to match the one in the screenshot but how do you adjust the travel distance of the knob to match the length of the track? Is that done in CSS too or some kind of JavaScript component?

Heres what I did. Explore the stuff and you may find solutions for your target. If not, just share your stuff and point out the problems.

The auto stuff applies via configuration, adding class switch-auto for them.

/*switch*/
.layout-row:not(.layout-xs-column)>.md-auto-horizontal-margin:not(:first-child),
.layout-xs-row>.md-auto-horizontal-margin:not(:first-child) {
margin-left: initial;
}
.layout-gt-xs-row:not(.layout-sm-column)>.md-auto-horizontal-margin:not(:first-child),
.layout-row:not(.layout-gt-xs-column):not(.layout-sm-column)>.md-auto-horizontal-margin:not(:first-child),
.layout-sm-row:not(.layout-sm-column)>.md-auto-horizontal-margin:not(:first-child) {
margin-left: initial;
}
.layout-gt-sm-row:not(.layout-md-column)>.md-auto-horizontal-margin:not(:first-child),
.layout-gt-xs-row:not(.layout-gt-sm-column):not(.layout-md-column)>.md-auto-horizontal-margin:not(:first-child),
.layout-md-row:not(.layout-md-column)>.md-auto-horizontal-margin:not(:first-child),
.layout-row:not(.layout-gt-xs-column):not(.layout-gt-sm-column):not(.layout-md-column)>.md-auto-horizontal-margin:not(:first-child)
{
margin-left: initial;
}

.layout-gt-md-row:not(.layout-lg-column)>.md-auto-horizontal-margin:not(:first-child),
.layout-gt-sm-row:not(.layout-gt-md-column):not(.layout-lg-column)>.md-auto-horizontal-margin:not(:first-child),
.layout-gt-xs-row:not(.layout-gt-sm-column):not(.layout-gt-md-column):not(.layout-lg-column)>.md-auto-horizontal-margin:not(:first-child),
.layout-lg-row:not(.layout-lg-column)>.md-auto-horizontal-margin:not(:first-child),
.layout-row:not(.layout-gt-xs-column):not(.layout-gt-sm-column):not(.layout-gt-md-column):not(.layout-lg-column)>.md-auto-horizontal-margin:not(:first-child)
{
margin-left: initial;
}

md-switch .md-container {
cursor: -webkit-grab;
cursor: grab;
width: 80px;
height: 30px;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-radius: 8px;
margin-right:0px;
box-shadow: 0px 5px 8px #00000045;
background: linear-gradient(0deg, rgba(78,78,78,1) 0%, rgba(215,215,215,1) 99%, rgba(236,236,236,1) 100%);
}
.switch-auto md-switch .md-container{
    width:100px;
}
md-switch .md-container::before {
content: "";
background-image:
url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc1JyBoZWlnaHQ9JzUnPgogIDxyZWN0IHdpZHRoPSc1JyBoZWlnaHQ9JzUnIGZpbGw9J3doaXRlJy8+CiAgPHBhdGggZD0nTTAgNUw1IDBaTTYgNEw0IDZaTS0xIDFMMSAtMVonIHN0cm9rZT0nIzg4OCcgc3Ryb2tlLXdpZHRoPScxJy8+Cjwvc3ZnPg==");
background-repeat: repeat;
position: absolute;
top: -18%;
right: 0px;
bottom: 0px;
left: 0px;
height: 134%;
opacity: 0.08;
border-radius: 9px;
transform: scaleY(0.75);
}
md-switch .md-container::after {
content: 'N®';
font-size: 14px;
font-family: monospace;
color: black;
text-shadow: 0 0 1px white;
position: absolute;
left: 2px;
top: 1px;
opacity: 0.4;
}
.switch-auto md-switch .md-container::after {
    content:'AUTO';
    opacity:0.8;
    left:4px;
}
.nr-dashboard-theme .nr-dashboard-switch md-switch .md-bar {
background-color: rgb(158, 158, 158 / 60%);
box-shadow: inset 1px 0.5px 8px #00000082;
}
md-switch .md-thumb-container {
top: 4px;
left: 18px;
width: 32px;
position: absolute;
transform: translate3d(0,0,0);
z-index: 1;
}
md-switch .md-thumb {
position: absolute;
margin: 0;
left: 0;
top: 0;
outline: none;
height: 22px;
width: 22px;
border-radius: 50%;
box-shadow: 0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12);
}
.nr-dashboard-theme .nr-dashboard-switch md-switch.md-checked .md-thumb {
background-color: rgb(158, 158, 158);
}
.switch-auto md-switch .md-thumb-container {
left: 36px;
width: 16px;
}
md-switch .md-thumb-container:after{
content:'';
position:absolute;
margin: 0;
left: 0;
top: 0;
outline: none;
height: 22px;
width: 22px;
border-radius: 50%;
background-color: rgb(158 158 158);
background-image: -webkit-radial-gradient( 50% 0%, 8% 50%, hsla(0,0%,100%,.5) 0%, hsla(0,0%,100%,0) 100%),
-webkit-radial-gradient( 50% 100%, 12% 50%, hsla(0,0%,100%,.6) 0%, hsla(0,0%,100%,0) 100%),
-webkit-radial-gradient( 0% 50%, 50% 7%, hsla(0,0%,100%,.5) 0%, hsla(0,0%,100%,0) 100%),
-webkit-radial-gradient( 100% 50%, 50% 5%, hsla(0,0%,100%,.5) 0%, hsla(0,0%,100%,0) 100%),

-webkit-repeating-radial-gradient( 50% 50%, 100% 100%, hsla(0,0%, 0%,0) 0%, hsla(0,0%, 0%,0) 3%, hsla(0,0%, 0%,.1)
3.5%),
-webkit-repeating-radial-gradient( 50% 50%, 100% 100%, hsla(0,0%,100%,0) 0%, hsla(0,0%,100%,0) 6%, hsla(0,0%,100%,.1)
7.5%),
-webkit-repeating-radial-gradient( 50% 50%, 100% 100%, hsla(0,0%,100%,0) 0%, hsla(0,0%,100%,0) 1.2%, hsla(0,0%,100%,.2)
2.2%),

-webkit-radial-gradient( 50% 50%, 200% 50%, hsla(0,0%,90%,1) 5%, hsla(0,0%,85%,1) 30%, hsla(0,0%,60%,1) 100%);
}
.nr-dashboard-theme .nr-dashboard-switch md-switch.md-checked .md-bar {
background-color: rgb(33 211 39 / 50%);
box-shadow: inset 1px 0.5px 8px #00000082;
}
md-switch .md-bar {
left: 24px;
width: 44px;
top: 7px;
height: 17px;
border-radius: 8px;
position: absolute;
}
.switch-auto md-switch .md-bar {
left:40px;
width:30px;
}

That looks like an awful lot of css for a switch :wink:
....well above my paygrade!

Well, it does not take that many if you do it from scratch of course. But for heavy override you just have no choice...

1 Like

And if you ever plan to ask the "why" question about it line by line - please put it in hold until next year.. I'm too busy to rip it apart and explain details.

Thank you for sharing the code here! That will give me a lot of ideas of how to control the look of these.

Does anyone else here know about how to control the movement length of the thumb knob when it is clicked? I am currently working through the code to try and get my head around how to extend it just a bit more.

Once I have something, I'll also post a quick example also.

Ok, I finally figured out a good solution. I created a template node and put the following inside:

<style>
    /* Settings */
    :root{
        --scale: 1;
        --height: 50px;
        --width: 100px;
        --knob-diameter: 45px;
        --background-inactive: rgb(173, 173, 173);
        --background-active: rgb(65, 207, 65);
        --knob-color-inactive: rgb(78, 78, 78);
        --knob-color-active: rgb(78, 78, 78);
        --text-color-inactive: black;
        --text-color-active: white;
        --text-size: 1rem;
        --transition-speed: 0.15s;
    }
    
    body{
        background: #e5e5e5;
        height: 100vh
    }
    
    h1{
        font-family: Arial, Helvetica, sans-serif;
        font-size: 3rem;
        text-align: center;
        margin: 50px 0;
    }
    
    /* Toggle switch body */
    .toggle{
        display: block;
        appearance: none;
        -webkit-appearance: none;
        -moz-appearance: none;
        width: var(--width);
        height: var(--height);
        background: var(--background-inactive);
        /* box-shadow: inset 3px 3px rgb(0,0,0); */
        border-radius: calc(var(--height) / 2);
        position: relative;
        transform: scale(var(--scale));
        transition-property: background;
        transition-duration: var(--transition-speed);
    }
    
    /* Toggle switch knob */
    .toggle:before{
        content: "";
        background: var(--knob-color-inactive);
        height: var(--knob-diameter);
        width: var(--knob-diameter);
        position: absolute;
        border-radius: 50%;
        top: calc((var(--height) - var(--knob-diameter)) / 2);
        left: calc((var(--height) - var(--knob-diameter)) / 2);
        transition: all var(--transition-speed);
        z-index: 2;
    }
    
    /* Toggle switch inactive text */
    .toggle:after{
        content: "OFF";
        position: absolute;
        font-size: var(--text-size);
        color: var(--text-color-inactive);
        font-weight: bold;
        top: 50%;
        left: 50%;
        transform: translate(25%, -50%);
        z-index: 1;
    }
    
    /* Toggle cwitch checked state */
    .toggle:checked{
        background: var(--background-active);
    }
    
    /* Toggle Switch Knob offset */
    .toggle:checked::before{
        left: calc((var(--height) - var(--knob-diameter)) / 2 + var(--width) - var(--height));
    }
    
    .toggle:checked::after{
        content: "ON";
        left: 5%;
        color: var(--text-color-active);
    }
</style>

<script type="text/javascript">
    //Declare once to make scope available in functions
    var theScope = scope;

    function handleChange(e) {
        const {checked} = e.target;
        //console.log(selected)
        theScope.send({payload:checked}) // send back to NR    
    }
</script>

<center><input type="checkbox" class="toggle" ng-click="send({payload: false})"></center>
<center><input type="checkbox" class="toggle" onchange="handleChange(event)" ng-checked="msg.payload"></center>
    

It also includes code to handle sending and receiving message payloads to get and set the toggle's state. All the style settings are in the :root selector at the top to customize the look of the toggle.

I hope this can be useful for others looking for similar functionality.

  • Jason O
1 Like

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