Button Component with RiotJS (Material Design)

Steeve - Mar 23 - - Dev Community

This article covers creating a Riot button component, using the Material Design CSS BeerCSS. Before starting, make sure you have a base application running, or read my previous article Setup Riot + BeerCSS + Vite.

These articles form a series focusing on RiotJS paired with BeerCSS, designed to guide you through creating components and mastering best practices for building production-ready applications. I assume you have a foundational understanding of Riot; however, feel free to refer to the documentation if needed: https://riot.js.org/documentation/

BeerCSS provides many button styles, such as "large", "outlined", "fill" and many more (see the following screenshot). The goal is to create a button component that dynamically supports all stylings and listens to click events.

Material Design Button from BeerCSS

Button Component Base

First, create a new file named c-button.riot under the components folder. The c- stands for "component", a useful naming convention and a good practice.

Into ./components/c-button.riot, write the minimum HTML and code for a Button:

<c-button>
    <button>
        <slot></slot>
    </button>
</c-button>
Enter fullscreen mode Exit fullscreen mode

Let's break down the code:

  • The <c-button> and </c-button> defined a custom root tag, with the same name as the file. You must write it; otherwise, it may create unexpected results. Using only or redefining native HTML tags is a bad practice, so starting c- is a good naming.
  • To print a custom Button name, a Slot <slot></slot> is used: The <slot> tag is a special Riot.js core feature that allows you to inject and compile the content of any custom component inside its template in runtime.

Then we can load and instantiate the c-button.riot, into a front page index.riot:

<index-riot>
    <div style="width:600px;padding:20px;">
        <h4 style="margin-bottom:20px">Riot + BeerCSS</h4>

        <c-button onclick={ clicked }>Home</c-button>
    </div>
    <script>
        import cButton from "./components/c-button.riot";

        export default {
            components: {
                cButton
            },
            clicked () {
                console.log("clicked");
            }
        }
    </script>
</index-riot>
Enter fullscreen mode Exit fullscreen mode

Code break-down:

  1. The component is imported with import cButton from "../components/c-button.riot" then loaded into the components:{} Riot object.
  2. On the HTML, the Button component is instantiated with <c-button onclick={ clicked }>Home</c-button>
  3. The event click is listened thanks to the onclick attribute: when the event is fired, the function clicked is executed.

Screenshot of the generated HTML with the custom button:

Button Component made with riotjs

With the simplest button, we can play by creating a counter. One value increased or decreased based on the button click, here are the HTML and Javascript on the index.riot file:

<index-riot>
    <div style="width:600px;padding:20px;">
        <h4 style="margin-bottom:20px">Riot + BeerCSS</h4>

        <c-button onclick={ add }>Add</c-button-test>
        <c-button>{state.number}</c-button-test>
        <c-button onclick={ sub }>Sub</c-button-test>
    </div>
    <script>
        import cButton from "./components/c-button.riot";

        export default {
            components: {
                cButton
            },
            state: {
                number: 0
            },
            add () {
                this.update({ number: this.state.number + 1 })
            },
            sub () {
                this.update({ number: this.state.number - 1 })
            }
        }
    </script>
</index-riot>
Enter fullscreen mode Exit fullscreen mode

Code Breakdown:

  1. To store the number value, it must be declared into the state: {} object.
  2. If the button Add is clicked, it will execute the add() function to add one to the number.
  3. If the button Sub is clicked, it will execute the sub() function to subtract the number by one.

Here is the generated HTML of the small counter:

Counter logic made with RiotJs

It is not necessary to create a add and sub function; the counting can be simplified in one line for each button with onclick={ () => update({ number: state.number + 1 }) }. The code for index.riot :

<index-riot>
    <div style="width:600px;padding:20px;">
        <h4 style="margin-bottom:20px">Riot + BeerCSS</h4>
        <c-button onclick={ () => update({ number: state.number + 1 }) }>Add</c-button-test>
        <c-button >{state.number}</c-button-test>
        <c-button onclick={ () => update({number: state.number - 1}) }>Sub</c-button-test>
    </div>
    <script>
        import cButton from "./components/c-button.riot";

        export default {
            components: {
                cButton
            },
            state: {
                number: 0
            }
        }
    </script>
</index-riot>
Enter fullscreen mode Exit fullscreen mode

Button Component Styles

Let's add a custom icon to the c-button.riot:

<c-button>
    <button>
        <i if={ props?.icon }> {props.icon} </i>
        <slot></slot>
    </button>
</c-button>
Enter fullscreen mode Exit fullscreen mode

If the props icon exists (HTML attribute), the <i> tag will be displayed with the icon symbol.

BeerCSS supports Material Fonts by default, here is the list of all icons: https://fonts.google.com/

Let's try the icon by defining the attribute "icon" with a symbol name into our index.riot, such as:

<c-button-test icon="add" onclick={ add }>Add</c-button-test>
<c-button-test>{state.number}</c-button-test>
<c-button-test icon="remove" onclick={ sub }>Sub</c-button-test>
Enter fullscreen mode Exit fullscreen mode

The generated HTML prints the add and minus (named removed) symbols:

Button components with icons add and remove

To create colour variations, we can add classes based on conditions: the following condition checks if the props.primary, props.secondary, or props.tiertiary exists, then it will add classes "primary", "secondary", or "tertiary" respectively.

<c-button>
    <button class="
            { props?.primary ? " primary" : null }
            { props?.secondary ? " secondary" : null }
            { props?.tertiary ? " tertiary" : null }
        ">
        <i if={ props?.icon }> {props.icon} </i>
        <slot></slot>
    </button>
</c-button>
Enter fullscreen mode Exit fullscreen mode

Now we can try each attribute color on the index.riot:

<c-button-test primary="true" icon="add" onclick={ add }>Add</c-button-test>
<c-button-test secondary="true">{state.number}</c-button-test>
<c-button-test tertiary="true" icon="remove" onclick={ sub }>Sub</c-button-test>
Enter fullscreen mode Exit fullscreen mode

The generated HTML displays three color variations:

Button with color variations

The logic for supporting all +30 variations (squared, circle, shadow elevation, etc.) is the same: Apply a class if the attribute exists.

The result of mixed button variation with Riot and BeerCSS:
Mixed variations of the button components made with RiotJS and BeerCss

The code of the Button component is available on Github:
https://github.com/steevepay/riot-beercss/blob/main/components/c-button.riot

Button Component Testing

It exist two methods for testing the Button component, and it is covered in two different articles:

Conclusion

Voilà 🎉 We created a Button Riot Component using Material Design elements with BeerCSS.

Feel free to comment if you have questions or need help about RiotJS.

Have a great day! Cheers 🍻

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .