A password field that warns when Caps Lock is on, read from key events, announced politely, linked to the field and gone on blur or when the key is toggled off.
registry/patterns/password-caps-lock-warning/password-caps-lock-warning.tsx
"use client"
import * as React from "react"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export interface PasswordCapsLockWarningProps extends Omit<
React.ComponentProps<typeof Input>,
"type" | "id"
> {
label?: string
autoComplete?: string
}
/**
* A password field that warns when Caps Lock is on, the commonest reason a
* correct password fails. The state comes from `getModifierState("CapsLock")`
* on key events, so it is only known after a key was pressed; the warning is a
* polite status linked to the field, goes away when the key is toggled off or
* the field loses focus, and never blocks typing.
*/
export function PasswordCapsLockWarning({
label = "Password",
autoComplete = "current-password",
onKeyDown,
onKeyUp,
onBlur,
...props
}: PasswordCapsLockWarningProps) {
const [caps, setCaps] = React.useState(false)
const id = React.useId()
const update = (event: React.KeyboardEvent<HTMLInputElement>) =>
setCaps(event.getModifierState?.("CapsLock") ?? false)
return (
<div className="grid gap-2">
<Label htmlFor={id}>{label}</Label>
<Input
id={id}
type="password"
autoComplete={autoComplete}
aria-describedby={`${id}-caps`}
onKeyDown={(event) => {
update(event)
onKeyDown?.(event)
}}
onKeyUp={(event) => {
update(event)
onKeyUp?.(event)
}}
onBlur={(event) => {
setCaps(false)
onBlur?.(event)
}}
{...props}
/>
<p
id={`${id}-caps`}
role="status"
className="text-xs font-medium text-amber-700 dark:text-amber-400"
>
{caps ? "Caps Lock is on" : ""}
</p>
</div>
)
}