Discovering Google’s Material-Web: Crafting a Custom Password Field
Introduction
In the rapidly evolving landscape of web development, utilizing pre-built component libraries has become a staple for building efficient and visually appealing applications. One such library that stands out is Google’s Material Web Components (MWC), often referred to as @material/web
. MWC provides a suite of web components that align with Google's Material Design guidelines, now enhanced by the latest Material 3 features such as dynamic color and enhanced accessibility. These components are versatile, working seamlessly across various frameworks like Lit, React, Vue, and Svelte, as well as different web environments like Django and Wordpress.
But what if you want to build something that Material-Web doesn’t offer out-of-the-box? This article aims to guide you through the process of creating a custom password input field with toggle visibility using Material-Web components. We’ll also be leveraging the capabilities of Lit, a library that allows you to create standard custom elements with scoped styles and reactive properties.
To make it even more developer-friendly, we’ll be using Vite as our build tool, setting up a Lit-based TypeScript project to encapsulate all the magic. So, let’s delve in and craft our custom password field.
Setting Up the Project
First things first, let’s set up our project. Open up your terminal and run:
npm create vite@latest my-project -- --template lit-ts
This one-liner gets you a Lit and TypeScript enabled project. Once the command finishes, go into your new project folder and kickstart the development server:
cd my-project
npm install
npm run dev
You’ll find a sample Lit element in the src/my-element.ts
file. That's our playground for the custom password field.
Adding Material-Web to Your Project
Next up, let’s get the Material-Web Components into our project. Install the library with:
npm install @material/web
For our custom password field, we’ll focus on two components: MdOutlinedTextField
and MdIconButton
. MdOutlinedTextField will serve as our password input field. We’ll place MdIconButton inside the text field to act as a password visibility toggle
Creating the Custom Password Field
Let’s start building our custom password field, step by step.
Extending MdOutlinedTextField
First off, VFPasswordField
extends MdOutlinedTextField
. We need to set some defaults like this.type="password";
and this.hasTrailingIcon = true;
in the constructor. These lines set the initial state and tell the component to expect an icon inside it.
import { html, css } from 'lit'
import {customElement, query} from 'lit/decorators.js';
import {MdOutlinedTextField} from '@material/web/textfield/outlined-text-field';
import {MdIconButton} from '@material/web/iconbutton/icon-button';
@customElement('my-password-field')
export class MyPasswordField extends MdOutlinedTextField {
constructor() {
super();
this.type="password";
this.hasTrailingIcon = true;
//...
}
}
Using the @customElement
decorator, we register our component as a custom HTML element in the browser.
Adding a Toggle Button
To actually insert the button, we override a private method called renderTrailingIcon
from MdOutlinedTextField
.
constructor() {
super();
this.type="password";
this.hasTrailingIcon = true;
(this as any).renderTrailingIcon = () => {
return html`
<span class="icon trailing" slot="end">
<md-icon-button toggle @change="${this.handleClick}">
<md-icon slot="selectedIcon">visibility_off</md-icon>
<md-icon>visibility</md-icon>
</md-icon-button>
</span>
`;
}
}
This gives us a toggle button within the password field — something not provided by the original MdOutlinedTextField
.
Customizing styles
To finesse the appearance, we add some styles in the static styles
section. Those styles would be incorporated into Shadow DOM, and would not have power outside our component.
@customElement('my-password-field')
class MyPasswordField extends MdOutlinedTextField {
static styles = [...MdOutlinedTextField.styles, css`
md-icon-button {
height: 26px;
padding-right: 8px;
min-inline-size: 16px;
}
`]
// ...
}
Here we extend MdOutlinedTextField.styles
and add own rule for our button inside
Handling Toggle Logic
Finally, let’s take care of the logic behind the toggle action. We use the @query
decorator to get a reference to the md-icon-button
element.
@query('md-icon-button')
button: MdIconButton | undefined;
With this reference, we implement the handleClick
method to switch the input type between text
and password
.
handleClick() {
this.type = this.button?.selected ? "text" : "password";
}
This method changes the input type based on the button’s state, either revealing or hiding the password as the user chooses.
Finalizing the Build
After crafting our custom password field, there are a couple more steps to ensure everything works seamlessly.
We need to import and then export the relevant components. This ensures they are included in the final build.
import {MdIconButton} from '@material/web/iconbutton/icon-button';
import {MdIcon} from '@material/web/icon/icon';
export {
MyPasswordField,
MdIconButton,
MdIcon,
}
Loading Icon Fonts. To display icons, add the following line to your index.html
:
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
This will load the Material Symbols font, allowing the visibility icons in our custom password field to display correctly.
At the last step, add our own custom component to the html page
<body>
<my-password-field
label="Password"
supporting-text="password field demo">
</my-password-field>
</body>
Running the Project
if you haven’t already, launch the development server.
npm run dev
Your custom password field should now be accessible at http://localhost:5173
or a similar URL depending on your setup.
Conclusion
So there we have it — a custom Material password field crafted with care. We extended the functionality of Google’s Material-Web components, specifically MdOutlinedTextField
, and slipped in a handy visibility toggle. While Material-Web is still in active development, it's already an incredibly useful tool. Its modular design not only allows for seamless integration but also provides a sturdy foundation to build self-contained, well-structured custom components. As the library matures, the potential for creating robust, user-friendly components only grows.