Forms
Password Input code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/password-input.blade.php
@props([
'name' => null,
'value' => null,
'placeholder' => '••••••••',
'size' => 'base',
'invalid' => false,
'disabled' => false,
'meter' => true,
'toggleable' => true,
])
@php
/*
| Password input with a show/hide toggle and a live strength meter. The
| meter scores length, case mix, digits and symbols (0–4 segments).
|
| Usage:
| <x-ui.password-input name="password" />
| <x-ui.password-input name="current_password" :meter="false" />
*/
$sizes = [
'sm' => 'text-sm py-1.5',
'base' => 'text-sm py-2.5',
'lg' => 'text-base py-3',
];
$ring = $invalid
? 'border-red-400 dark:border-red-500 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 dark:border-gray-700 focus:border-brand-500 focus:ring-brand-500';
$classes = implode(' ', [
'block w-full rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 shadow-sm transition focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed',
$sizes[$size] ?? $sizes['base'],
'ps-3.5 pe-10',
$ring,
]);
$inputId = $attributes->get('id') ?: $name;
@endphp
<div
x-data="{
show: false,
value: @js((string) ($value ?? '')),
get score() {
const v = this.value;
if (! v) { return 0; }
let points = 0;
if (v.length >= 8) { points++; }
if (v.length >= 12) { points++; }
if (/[a-z]/.test(v) && /[A-Z]/.test(v)) { points++; }
if (/\d/.test(v)) { points++; }
if (/[^A-Za-z0-9]/.test(v)) { points++; }
if (points <= 1) { return 1; }
if (points === 2) { return 2; }
if (points <= 4) { return 3; }
return 4;
},
get strengthLabel() {
return ['', 'Weak', 'Fair', 'Good', 'Strong'][this.score];
},
get barClass() {
return ['', 'bg-red-500', 'bg-amber-500', 'bg-accent-500', 'bg-green-500'][this.score];
},
get labelClass() {
return ['text-gray-400', 'text-red-500', 'text-amber-500', 'text-accent-500', 'text-green-500'][this.score];
},
}"
class="{{ $attributes->get('class') }}"
>
<div class="relative">
<input
:type="show ? 'text' : 'password'"
x-model="value"
@disabled($disabled)
@if ($inputId) id="{{ $inputId }}" @endif
@if ($name) name="{{ $name }}" @endif
placeholder="{{ $placeholder }}"
@if ($invalid) aria-invalid="true" @endif
autocomplete="new-password"
{{ $attributes->except(['class', 'id', 'name', 'placeholder']) }}
class="{{ $classes }}"
>
@if ($toggleable)
<button
type="button"
@click="show = ! show"
:aria-label="show ? 'Hide password' : 'Show password'"
:aria-pressed="show.toString()"
class="absolute inset-y-0 end-0 flex items-center pe-3 text-gray-400 transition hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 cursor-pointer"
>
<x-ui.icon name="eye" size="base" x-show="! show" />
<x-ui.icon name="eye-slash" size="base" x-show="show" x-cloak />
</button>
@endif
</div>
@if ($meter)
<div class="mt-2 flex items-center gap-2" aria-live="polite">
<div class="flex flex-1 gap-1.5" aria-hidden="true">
@foreach ([1, 2, 3, 4] as $segment)
<span
class="h-1 flex-1 rounded-full transition-colors"
:class="score >= {{ $segment }} ? barClass : 'bg-gray-200 dark:bg-gray-800'"
></span>
@endforeach
</div>
<span class="text-xs font-medium text-gray-400" :class="labelClass" x-text="strengthLabel"></span>
</div>
@endif
</div>