Allow me to show you how to build a modal window with Stencil.
Coding with relatively new tools can be challenging due to the lack of (good) tutorials. Especially when you have a specific thing like a modal overlay in mind.
So, that’s why I’ve decided to build my own modal overlay component with StencilJS and share my experiences and to write this tutorial to help you understand the possibilities of StencilJS.
Check out this repo for the source.
Stencil is a compiler that generates Web Components which combines the best concepts of the most popular frameworks into a simple build-time tool. It provides extra API's that makes writing fast components simpler. APIs like Virtual DOM, JSX, and async rendering make fast, powerful components easy to create, while still maintaining 100% compatibility with Web Components.
The developer experience is also tuned and comes with life reload and a small dev server baked into the compiler.
Stencil was created by the Ionic Framework team to help build faster, more capable components that worked across all major frameworks.
One common and frequently used UI component is a modal window, a content container that displays above the rest of the content and contains a clear call to action. It’s sometimes accompanied by an overlay that covers the rest of the web page or app. And that is what we’re going to build today!
The name of our component is
my-component. This is the default name generated by the starter and for convenience and keeping this tutorial in scope, I’ve decided to let the name as is. You’re completely free to rename it at any time.
It has the following attributes:
Open<boolean>: Shows the modal window component;
Transparent<boolean>: Toggles transparency of the overlay;
The components has the following method:
render: Renders content to the screen.
Before we can start building the component we have to set up a development environment, which is very easy with the starter, provided by our friends from Stencil.
Note: You need npm v6 or higher in order to complete this tutorial!
Stencil can be used to create standalone components or entire apps. Open a new terminal window and run the following command:
npm init stencil
After running init you will be provided with a prompt so that you can choose the type of project to start.
Since we’re building a single component, select the third option, which generates a development environment, installs all necessary dependencies and scaffolds the component’s code.
The next step is to provide a name for the project. For this tutorial, it doesn’t really matter which name you pick. I wanted to be extremely original and named my project:
Stencil provides the developer with a very basic, hello world example to understand a bit better what’s going on and how an application is organized. It’s not in the tutorial’s scope to elaborate on this, but you can read more about this here.
Alright! We’re done setting up our application’s infrastructure!
Since we’re creating a component TDD style, let’s start off right away with writing our first tests.
Stencil provides many utility functions to help test Jest and Puppeteer. For example, a component’s Shadow Dom can be queried and tested with the Stencil utility functions built on top of Puppeteer. Tests can not only be provided mock HTML content, but they can also go to URLs of your app which Puppeteer is able to open up and test on Stencil’s dev server.
The starter generated a test file already (
./src/components/my-component/my-component.e2e.ts), which contains a few basic unit tests to get the gist on testing Web Components. Open this file, study it and replace it with the following content:
- We import the necessary packages from the testing libraries provided in Stencil core.
- We create a my-component element and append it to the DOM. This is done in the beforeEach method, which is called before every unit test.
- We expect that my-component successfully renders in the DOM.
- We expect that we find a div decorated with a class, called overlay.
Let’s run our tests with the following command:
npm run test
…Only to see that all of them fail. So let’s change that immediately!
./src/components/my-component/my-component.tsx, study the example code and replace it with the following:
Notice the following parts of the component:
@Componentdecorator. This decorator provides metadata about our component to the compiler.
- In the class you find the
render()function. This is used by the component to render content to the screen. It returns JSX.
- In the HTML template, you find a
<slot/>container, which is a placeholder inside a web component that you can fill with your own markup.
Learn more about Stencil components here.
If we run the tests again, they all pass. Hooray! Now it’s time to implement more logic and make our component actually useful.
Before we start implementing the logic for opening the modal, let’s write some more tests.
We want to cover the following cases:
- It should display the overlay while the modal is open.
- If set, the overlay should be transparent.
This results in the following test cases, which you need to add to the test file:
Whoah! What happened here?
- We set different properties (
transparent) with the
- We wait for the changes made to the component with the
waitForChanges()method. Both Stencil and Puppeteer have an asynchronous architecture, which is a good thing for performance. Since all calls are async, it's required that
await page.waitForChanges()is called when changes are made to components.
- We check if the element is decorated with the expected CSS classes.
Read more about testing Stencil components here.
And, of course, if we run our tests they’ll fail miserably again, so let’s open the component’s code (
my-component.tsx) and make the tests pass.
What did we do?
- We’ve added properties
transparent. They can be recognized by the
@Prop()decorator, a class that’s imported from
- We’ve changed our class definition in the HTML template and check if we need to make the modal visible and make the overlay transparent.
In order to close the modal, we need to set the
open property to
false. We’ll implement a method for that in our sample code later.
Let’s write the tests necessary and make them pass:
All tests are in green again and we have a fully operating modal, which looks terrible…
Add the following styling classes to
Looks much better now!
All that we’ve done is writing tests and make them pass by adding code to the component, but the real proof is to check if it actually works, so let’s update our
Here we create an instance of the component itself and decorate it with an id in order to be able to access it later on. We also added a button, which acts as a trigger to open the modal.
In the script, we created two references. One for the modal component and one for the button. Next, we’ve created two events to test if opening and closing it works properly.
Last, but not least, we added an
eventListener to the modal itself, which listens for a
click event. If it’s triggered, the modal will close.
That’s it! There’s much room to improve this component, like extending the modal content container template with a header and footer, cancel/confirmation buttons, etc. etc. If you see any points for improvement or spot a mistake in my code, please please leave create a pull request or leave a message in the comments!
Feel free to check out the code in this git repository.
Stefan helps developers to become Framework Agnostic. If you find his content helpful, you can buy him a coffee here and get his exclusive e-book "10 reasons to go framework-agnostic" for free!