Button Component with RiotJS (Material Design)
Tags: riotjs, javascript, beginners, frontend
Published: 2024-03-23 14:53:41

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](https://dev.to/steeve/riotjs-material-design-how-to-setup-beercss-269c).

> These articles form a series focusing on [RiotJS](https://riot.js.org/) paired with [BeerCSS](https://www.beercss.com/), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/botbou1h9qe6286bbwkp.png)

## 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:

```html
<c-button>
    <button>
        <slot></slot>
    </button>
</c-button>
```
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 <button> 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**:
```html
<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>
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ue5nw4plcqk5lw34e284.png)

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:
```html
<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>
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ddlet52dp9w4dpvkf6db.png)

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** :
```html
<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>
```

## Button Component Styles

Let's add a custom icon to the `c-button.riot`:
```html
<c-button>
    <button>
        <i if={ props?.icon }> {props.icon} </i>
        <slot></slot>
    </button>
</c-button>
```
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:
```html
<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>
```
The generated HTML prints the **add** and **minus** (named removed) symbols:

![Button components with icons add and remove](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a9txeqa5jx32jzec5xqc.png)

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.
```html
<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>
```
Now we can try each attribute color on the **index.riot**:
```html
<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>
```
The generated HTML displays three color variations:

![Button with color variations](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/84j1poz7x6a9zswbjn5a.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xmkis41274wt8hv3ao03.png)

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:
* [Test with Vitest and Riot-SSR in a Node Environment](https://dev.to/steeve/riot-component-unit-test-with-vitest-node-env-hk0)
* [Test with Vitest in a JsDom Environment](https://dev.to/steeve/riot-component-unit-test-with-vitest-jsdom-env-182l)

## 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 🍻