Introduction
Learn how to give clients the ability to show/hide password inputs. Some people must see what they are typing, not be in a position where someone is shoulder surfing to see what they are typing.
Checkboxes will not be used for the provided demonstrations; instead, a button with an image will be used.
Basic example
The following example is in the project MockupApplication, which includes styles and JavaScript on the page. For a real application, the styles and JavaScript need to be in separate include files.
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<title>Toggle Password Visibility</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<style>
.form-label,
.password-container > label {
display: block;
margin-bottom: .375rem;
}
.input-group .btn {
display: flex;
align-items: center;
justify-content: center;
}
.toggle-eye {
line-height: 1;
font-size: 1.1rem;
pointer-events: none;
}
</style>
<div class="container">
<h1 class="fs-4">Sign In</h1>
<form>
<div class="row mb-3">
<div class="col-3">
<label class="form-label" for="userID">User name</label>
<input type="text" name="userID" id="userID" class="form-control">
</div>
</div>
<div class="row mb-3">
<div class="col-3">
<div class="password-container">
<label for="Password" class="form-label">Password:</label>
<div class="input-group">
<input id="Password"
name="Password"
type="password"
class="form-control"
dir="ltr"
style="text-align: left;" />
<button type="button"
class="btn btn-primary"
data-toggle="password"
data-target="#Password"
aria-label="Show password"
aria-pressed="false">
<i class="bi bi-eye toggle-eye" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-3">
<button type="submit"
id="submit"
class="btn btn-primary float-end">
Log In
</button>
</div>
</div>
</form>
</div>
@section Scripts {
<script>
(function () {
function togglePassword(btn) {
const target = btn.getAttribute('data-target');
const input = target ? document.querySelector(target) : null;
if (!input) return;
const icon = btn.querySelector('i.bi');
const makeVisible = input.type === 'password';
input.type = makeVisible ? 'text' : 'password';
btn.setAttribute('aria-pressed', String(makeVisible));
btn.setAttribute('aria-label', makeVisible ? 'Hide password' : 'Show password');
if (icon) {
icon.classList.toggle('bi-eye', !makeVisible);
icon.classList.toggle('bi-eye-slash', makeVisible);
}
}
document.addEventListener('click', function (e) {
const btn = e.target.closest('[data-toggle="password"]');
if (btn) togglePassword(btn);
});
})();
</script>
}
The code is straightforward and easy to implement into an application.
Options example
The following uses a JavaScript plugin, Bootstrap Show Password.
Provides
- Placement, left or right of password input
- Size of button to show and hide password
- Message to display for an empty password input
- Custom eye class
- Events and methods
The source code has styles and JavaScript on the page, as in the basic example, and should be moved to include files. Note the element to display when events are triggered rather than the need for alerts or opening up developer tools and see events triggered in the console window.
@page
@using HideShowPasswordSample.Pages
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="container">
<div class="alert alert-primary" role="alert">
<h1 class="fs-5">Password toggles</h1>
</div>
<form method="post">
<div class="row mb-3">
<div class="col-4">
<label class="form-label" for="userID">User name</label>
<input type="text" name="userID" id="userID" class="form-control">
</div>
</div>
<div class="row mb-3">
<div class="col-4">
<label for="password1">Standard placement</label>
<input id="password1"
data-toggle="password"
class="form-control"
type="password"
maxlength="20"
data-size="sm" @* use size small for input/eye remove for default size or use lg *@
placeholder="Enter the password"
name="PasswordContainer.Password1"
value="@Model.PasswordContainer.Password1">
</div>
</div>
<div class="row mb-3">
<div class="col-4">
<label for="password2">left placement</label>
<input id="password2"
data-toggle="password"
data-placement="before" @* placed eye before input *@
class="form-control"
type="password"
data-size="sm"
placeholder="Enter the password"
name="PasswordContainer.Password2"
value="@Model.PasswordContainer.Password2">
</div>
</div>
<div class="row mb-3">
<div class="col-4">
<label for="password3">Custom placeholder</label>
<input id="password3"
data-toggle="password"
data-placement="before"
class="form-control"
type="password"
data-size="sm"
placeholder="your password" name="PasswordContainer.Password3"
value="@Model.PasswordContainer.Password3">
</div>
</div>
<div class="row mb-3">
<div class="col-4">
<div class="fw-bold text-primary">Event log for show/hide passwords</div>
<pre id="passwordLog"
class="border p-2"
style="height: 150px; overflow: auto;">
</pre>
</div>
</div>
<div class="row">
<div>
<input type="submit" value="Log in" class="btn btn-primary"/>
</div>
</div>
</form>
</div>
@section Scripts
{
<script>
$(function () {
var $log = $('#passwordLog');
function writeLog(message) {
var current = $log.text();
$log.text(current + message + "\n");
$log.scrollTop($log[0].scrollHeight);
}
$('input[data-toggle="password"]').each(function () {
var $input = $(this);
// Try to find the plugin's eye toggle next to the input
var $toggle = $input
.siblings('button')
.add($input.siblings('.input-group-append').find('.btn'))
.add($input.siblings('.input-group-prepend').find('.btn'))
.first();
if ($input.is(':disabled')) {
// Ensure no event handlers are attached
$input.off('show.bs.password hide.bs.password');
// Visually and functionally disable the eye toggle if present
if ($toggle.length) {
$toggle
.attr('disabled', 'disabled')
.attr('aria-disabled', 'true')
.attr('title', 'Password field is disabled')
.addClass('disabled');
// Block clicks just in case
$toggle.on('click.password-block', function (e) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
});
}
// Skip binding
return;
}
// Enabled inputs: bind plugin events (do NOT call .password() here)
$input
.on('show.bs.password', function () {
writeLog(`show.bs.password fired for #${this.id}`);
})
.on('hide.bs.password', function () {
writeLog(`hide.bs.password fired for #${this.id}`);
});
});
});
</script>
<style>
/*
* Make disabled toggle clearly inert even if theme doesn't handle it
* Can be placed in site-wide CSS if desired or a page specific .css file
*/
.btn.disabled, .btn[disabled] {
pointer-events: none;
opacity: .5;
}
</style>
}
Model
public record PasswordContainer
{
public string? Password1 { get; set; }
public string? Password2 { get; set; }
public string? Password3 { get; set; }
}
Code behind
public class IndexModel : PageModel
{
[BindProperty]
public required PasswordContainer PasswordContainer { get; set; }
public void OnGet()
{
PasswordContainer = new PasswordContainer()
{
Password1 = "MySecretPassword1",
Password2 = "MySecretPassword2",
Password3 = "MySecretPassword3"
};
}
public void OnPost()
{
AnsiConsole.MarkupLine(PasswordContainer.Password1 is not null
? $"[yellow]Password 1:[/] {PasswordContainer.Password1}"
: "[red]Password 1 is null[/]");
AnsiConsole.MarkupLine(PasswordContainer.Password2 is not null
? $"[yellow]Password 2:[/] {PasswordContainer.Password2}"
: "[red]Password 2 is null[/]");
AnsiConsole.MarkupLine(PasswordContainer.Password3 is not null
? $"[yellow]Password 3:[/] {PasswordContainer.Password3}"
: "[red]Password 3 is null[/]");
}
}
Summary
Two different approaches have been presented. Select the one that best suits your needs, along with changing styles to fit into your application theme.
Top comments (0)