Formsui
Secret input
Masked API key or secret field with reveal and copy.
Pooled connection string for the main branch.
Installation
npx shadcn@latest add @particular/secret-inputnpm dependencies
- cn@^0.4.0
- lucide-react@^1.51.0
Installs these registry items
Source
"use client"
import * as React from "react"
import { EyeIcon, EyeOffIcon } from "lucide-react"
import { cn } from "cn"
import { CopyButton } from "@/components/ui/copy-button"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group"
type SecretInputProps = Omit<React.ComponentProps<"input">, "type" | "value" | "defaultValue" | "onCopy"> & {
value: string
revealed?: boolean
defaultRevealed?: boolean
onRevealedChange?: (revealed: boolean) => void
copyable?: boolean
onCopied?: (value: string) => void
}
function SecretInput({
value,
revealed: revealedProp,
defaultRevealed = false,
onRevealedChange,
copyable = true,
onCopied,
readOnly,
className,
...props
}: SecretInputProps) {
const [uncontrolled, setUncontrolled] = React.useState(defaultRevealed)
const revealed = revealedProp ?? uncontrolled
const toggle = () => {
const next = !revealed
if (revealedProp === undefined) setUncontrolled(next)
onRevealedChange?.(next)
}
return (
<InputGroup data-slot="secret-input" className={className}>
<InputGroupInput
type={revealed ? "text" : "password"}
value={value}
readOnly={readOnly ?? props.onChange === undefined}
autoComplete="off"
spellCheck={false}
className={cn("font-mono text-[0.8125rem]", !revealed && "tracking-wider")}
{...props}
/>
<InputGroupAddon align="inline-end">
<InputGroupButton
size="icon-xs"
aria-label={revealed ? "Hide value" : "Show value"}
aria-pressed={revealed}
onClick={toggle}
>
{revealed ? <EyeOffIcon /> : <EyeIcon />}
</InputGroupButton>
{copyable && <CopyButton value={value} size="icon-xs" label="Copy value" onCopied={onCopied} />}
</InputGroupAddon>
</InputGroup>
)
}
export { SecretInput, type SecretInputProps }