HextaUI

BetaGebaut auf Vercels UI SDK 0.1.0-beta.2. Die API kann sich ändern.

Password input

Ein Passwortfeld auf Vercels UI SDK mit Ein-/Ausblenden-Schalter, Feststelltasten-Hinweis, Stärkeanzeige und dem passenden Autocomplete für Passwortmanager.

Abhängig von
  • @tabler/icons-react
  • @ui-sdk/core
  • @ui-sdk/react
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputCapsLock,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputDemo() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="demo-password">Password</Label>
      <PasswordInput>
        <PasswordInputGroup>
          <PasswordInputControl id="demo-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
        <PasswordInputCapsLock />
      </PasswordInput>
    </div>
  )
}

export { UiSdkPasswordInputDemo }
pnpm dlx shadcn@latest add https://hextaui.com/r/ui-sdk-password-input.json

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

UI SDK bringt etwas eigenes CSS in einem Cascade Layer namens ui-sdk mit. Deklariere diesen Layer ganz oben im globalen Stylesheet, über dem Tailwind-Import, damit deine Klassen gewinnen.

@layer ui-sdk;

@import "tailwindcss";
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"
<PasswordInput name="password">
  <PasswordInputGroup>
    <PasswordInputControl aria-label="Password" />
    <PasswordInputToggle />
  </PasswordInputGroup>
</PasswordInput>

Das Steuerelement bleibt ein natives Passwortfeld, also funktionieren Passwortmanager, Autofill und Formularversand wie überall. Formularoptionen wie name, required und autoComplete gehören auf PasswordInput, Input-Attribute wie id und placeholder auf PasswordInputControl.

autoComplete ist standardmäßig current-password zum Anmelden. Nutze new-password zum Registrieren oder Ändern, damit der Browser ein starkes Passwort vorschlägt, und off für Geheimnisse, die keine Kontopasswörter sind.

PasswordInput
├── PasswordInputGroup
│   ├── PasswordInputControl
│   └── PasswordInputToggle
├── PasswordInputCapsLock
└── PasswordInputStrength

PasswordInputStrength bewertet das Passwort beim Tippen von 0 bis 4. Die Checkliste liest denselben Wert mit usePasswordInputValue(). Die Regeln gehören zum Beispiel, denn die Komponente setzt keine Passwortrichtlinie.

"use client"

import { IconCheck, IconCircleDashed } from "@tabler/icons-react"

import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputCapsLock,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputStrength,
  PasswordInputToggle,
  usePasswordInputValue,
} from "@/components/ui/ui-sdk-password-input"

const rules = [
  {
    label: "At least 12 characters",
    test: (password: string) => password.length >= 12,
  },
  { label: "A number", test: (password: string) => /\d/.test(password) },
  {
    label: "An uppercase and a lowercase letter",
    test: (password: string) =>
      /[a-z]/.test(password) && /[A-Z]/.test(password),
  },
]

function Requirements() {
  const password = usePasswordInputValue()
  return (
    <ul
      aria-label="Password requirements"
      className="m-0 flex list-none flex-col gap-1 p-0 pt-3 text-xs"
    >
      {rules.map((rule) => {
        const done = rule.test(password)
        return (
          <li
            key={rule.label}
            className={
              done
                ? "flex items-center gap-1.5 text-foreground"
                : "flex items-center gap-1.5 text-muted-foreground"
            }
          >
            {done ? (
              <IconCheck aria-hidden="true" className="size-3.5 text-success" />
            ) : (
              <IconCircleDashed aria-hidden="true" className="size-3.5" />
            )}
            <span className="sr-only">{done ? "Done:" : "To do:"}</span>
            {rule.label}
          </li>
        )
      })}
    </ul>
  )
}

function UiSdkPasswordInputNewPassword() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="create-password">Create a password</Label>
      <PasswordInput autoComplete="new-password">
        <PasswordInputGroup>
          <PasswordInputControl id="create-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
        <PasswordInputCapsLock />
        <PasswordInputStrength />
        <Requirements />
      </PasswordInput>
    </div>
  )
}

export { UiSdkPasswordInputNewPassword }

Kombiniere es mit einem E-Mail-Feld mit autoComplete="username". Enter im Passwort sendet das Formular, und beim Senden wird ein sichtbares Passwort wieder verborgen, damit der Browser das Speichern anbietet.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputCapsLock,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputSignIn() {
  const [message, setMessage] = React.useState<string | null>(null)

  return (
    <form
      className="flex w-full max-w-64 flex-col gap-4"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        const length = String(data.get("password")).length
        setMessage(
          `Signed in as ${data.get("email")}, with a password of ${length} characters.`
        )
      }}
      onReset={() => setMessage(null)}
    >
      <div className="flex flex-col gap-2">
        <Label htmlFor="sign-in-email">Email</Label>
        <Input
          id="sign-in-email"
          name="email"
          type="email"
          autoComplete="username"
          required
        />
      </div>
      <div className="flex flex-col gap-2">
        <Label htmlFor="sign-in-password">Password</Label>
        <PasswordInput name="password" required>
          <PasswordInputGroup>
            <PasswordInputControl id="sign-in-password" />
            <PasswordInputToggle />
          </PasswordInputGroup>
          <PasswordInputCapsLock />
        </PasswordInput>
      </div>
      <div className="flex items-center gap-3">
        <Button type="submit" size="sm">
          Sign in
        </Button>
        <Button type="reset" size="sm" variant="ghost">
          Reset
        </Button>
      </div>
      <p className="m-0 text-sm text-muted-foreground">
        {message ?? "Press Enter in the password to submit."}
      </p>
    </form>
  )
}

export { UiSdkPasswordInputSignIn }

defaultVisible startet mit sichtbarem Text, für ein Geheimnis, das Leute lesen müssen, etwa ein WLAN-Passwort.

import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputVisible() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="wifi-password">Wi-Fi password</Label>
      <PasswordInput
        defaultVisible
        defaultValue="maple-orbit-42"
        autoComplete="off"
      >
        <PasswordInputGroup>
          <PasswordInputControl id="wifi-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
      </PasswordInput>
      <p className="m-0 text-xs text-muted-foreground">
        Guests read it from here, so it starts shown.
      </p>
    </div>
  )
}

export { UiSdkPasswordInputVisible }

Ein Controller aus createPasswordInputController() setzt, zeigt, verbirgt, leert und fokussiert das Feld von jedem Button aus. Schreibvorgänge laufen wie Tippen über onValueChange.

"use client"

import * as React from "react"
import { IconSparkles } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputStrength,
  PasswordInputToggle,
  createPasswordInputController,
} from "@/components/ui/ui-sdk-password-input"

const alphabet =
  "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789-_!?"

function generatePassword(length = 20) {
  const bytes = crypto.getRandomValues(new Uint32Array(length))
  return Array.from(bytes, (byte) => alphabet[byte % alphabet.length]).join("")
}

function UiSdkPasswordInputController() {
  const [password] = React.useState(createPasswordInputController)

  return (
    <div className="flex w-full max-w-72 flex-col gap-2">
      <div className="flex items-center justify-between gap-2">
        <Label htmlFor="generated-password">New password</Label>
        <Button
          size="xs"
          variant="ghost"
          onClick={() => {
            password.setValue(generatePassword())
            password.setVisible(true)
            password.focus()
          }}
        >
          <IconSparkles aria-hidden="true" />
          Suggest
        </Button>
      </div>
      <PasswordInput controller={password} autoComplete="new-password">
        <PasswordInputGroup>
          <PasswordInputControl id="generated-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
        <PasswordInputStrength />
      </PasswordInput>
      <div className="flex gap-2 pt-1">
        <Button
          size="sm"
          variant="outline"
          onClick={() => password.toggleVisible()}
        >
          Toggle
        </Button>
        <Button size="sm" variant="ghost" onClick={() => password.clear()}>
          Clear
        </Button>
      </div>
    </div>
  )
}

export { UiSdkPasswordInputController }

Übergib visible und onVisibleChange. Der Grund unterscheidet einen Schalterdruck vom Absenden, einem Reset oder dem Verbergen der Seite.

"use client"

import * as React from "react"

import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputControlled() {
  const [visible, setVisible] = React.useState(false)
  const [reason, setReason] = React.useState<string | null>(null)

  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="controlled-password">Password</Label>
      <PasswordInput
        visible={visible}
        onVisibleChange={(next, details) => {
          setVisible(next)
          setReason(details.reason)
        }}
      >
        <PasswordInputGroup>
          <PasswordInputControl id="controlled-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
      </PasswordInput>
      <p className="m-0 text-xs text-muted-foreground">
        {reason
          ? `${visible ? "Shown" : "Hidden"} by ${reason}.`
          : "Hidden. Press the eye to show it."}
      </p>
    </div>
  )
}

export { UiSdkPasswordInputControlled }

invalid markiert das Feld und setzt aria-invalid auf das Input. Richte sein aria-describedby auf deine Meldung. Ein Pflichtfeld wird auch rot, wenn es leer verlassen wird.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputInvalid() {
  const [invalid, setInvalid] = React.useState(true)

  return (
    <form
      className="flex w-full max-w-64 flex-col gap-2"
      onSubmit={(event) => {
        event.preventDefault()
        const password = new FormData(event.currentTarget).get("password")
        setInvalid(password !== "correct-horse")
      }}
    >
      <Label htmlFor="checked-password">Password</Label>
      <PasswordInput
        name="password"
        invalid={invalid}
        onValueChange={() => setInvalid(false)}
      >
        <PasswordInputGroup>
          <PasswordInputControl
            id="checked-password"
            aria-describedby={invalid ? "checked-password-error" : undefined}
          />
          <PasswordInputToggle />
        </PasswordInputGroup>
      </PasswordInput>
      {invalid ? (
        <p
          id="checked-password-error"
          className="m-0 text-xs text-destructive"
        >
          That password isn’t right. Try “correct-horse”.
        </p>
      ) : null}
      <Button type="submit" size="sm" className="mt-1 self-start">
        Unlock
      </Button>
    </form>
  )
}

export { UiSdkPasswordInputInvalid }

Ein schreibgeschütztes Feld lässt sich fokussieren, anzeigen und kopieren, aber nicht ändern, und wird trotzdem gesendet.

import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputReadOnly() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="recovery-key">Recovery key</Label>
      <PasswordInput readOnly defaultValue="7Hq2-vX9p-Lm4c" autoComplete="off">
        <PasswordInputGroup>
          <PasswordInputControl id="recovery-key" />
          <PasswordInputToggle aria-label="Show recovery key" />
        </PasswordInputGroup>
      </PasswordInput>
      <p className="m-0 text-xs text-muted-foreground">
        You can show it, but not change it.
      </p>
    </div>
  )
}

export { UiSdkPasswordInputReadOnly }

Tab überspringt ein deaktiviertes Feld und seinen Schalter, und der Wert wird nicht gesendet.

import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputDisabled() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="managed-password">Password</Label>
      <PasswordInput disabled defaultValue="hunter22">
        <PasswordInputGroup>
          <PasswordInputControl id="managed-password" />
          <PasswordInputToggle />
        </PasswordInputGroup>
      </PasswordInput>
      <p className="m-0 text-xs text-muted-foreground">
        Your administrator manages this password.
      </p>
    </div>
  )
}

export { UiSdkPasswordInputDisabled }

Der Schalter sitzt links, und die Stärkeanzeige füllt sich von rechts. Übergib Labels in deiner Sprache.

import { Label } from "@/components/ui/label"
import {
  PasswordInput,
  PasswordInputCapsLock,
  PasswordInputControl,
  PasswordInputGroup,
  PasswordInputStrength,
  PasswordInputToggle,
} from "@/components/ui/ui-sdk-password-input"

function UiSdkPasswordInputRtl() {
  return (
    <div dir="rtl" className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="rtl-password">كلمة المرور</Label>
      <PasswordInput autoComplete="new-password">
        <PasswordInputGroup>
          <PasswordInputControl id="rtl-password" />
          <PasswordInputToggle aria-label="إظهار كلمة المرور" />
        </PasswordInputGroup>
        <PasswordInputCapsLock>مفتاح الأحرف الكبيرة مفعّل</PasswordInputCapsLock>
        <PasswordInputStrength
          label="قوة كلمة المرور"
          labels={["", "ضعيفة", "مقبولة", "جيدة", "قوية"]}
        />
      </PasswordInput>
    </div>
  )
}

export { UiSdkPasswordInputRtl }
TasteAktion
TabSpringt vom Feld zum Schalter.
SpaceEnterBlendet das Passwort über den Schalter ein oder aus und lässt den Fokus darauf.
EnterSendet das Formular aus dem Feld. Es drückt nie den Schalter.

Der Schalter ist ein Umschaltbutton mit festem Namen, „Show password“, und aria-pressed sagt Screenreadern, ob das Passwort sichtbar ist. Benenne ihn für andere Geheimnisse und Sprachen um. Ein Zeigerdruck auf den Schalter lässt die Einfügemarke im Feld, wo sie war.

PasswordInputCapsLock öffnet sich unter dem Feld, solange die Feststelltaste aktiv ist und das Feld den Fokus hat, und meldet das über eine Statusregion. PasswordInputStrength ist eine Anzeige, deren Werttext das Stärke-Label ist; es wird bei Bedarf gelesen statt bei jeder Taste angesagt. Sichtbare Passwörter überspringen Rechtschreibprüfung, Autokorrektur und Großschreibung.

Kein Teil schreibt das Passwort in ein data-*-Attribut. Wurzel und Steuerelement tragen nur Booleans wie data-filled, die Anzeige trägt die Punktzahl. Ein kontrolliertes value lässt React allerdings ein value-Attribut im DOM halten, lies das Passwort also mit onValueChange oder usePasswordInputValue().

Jeder Teil umhüllt den gleichnamigen UI-SDK-Teil und nimmt alle seine Props an, auch render und einen className, der eine Funktion des Zustands sein kann.

PropTypStandard
value
string–
defaultValue
string–
onValueChange
(value: string, details) => void–
visible
boolean–
defaultVisible
booleanfalse
onVisibleChangedetails.reason ist toggle-press, form-submit, reset, page-hide oder imperative.
(visible: boolean, details) => void–
autoComplete
"current-password" | "new-password" | "off" | string"current-password"
invalid
booleanfalse
disabled
booleanfalse
readOnly
booleanfalse
required
booleanfalse
name
string–
form
string–
controllerAus createPasswordInputController().
PasswordInputController–
AttributBeschreibung
data-slot="password-input"Spricht das Passwortfeld in CSS an.
data-visibleVorhanden, solange das Passwort angezeigt wird.
data-capslockVorhanden, solange die Feststelltaste aktiv ist und das Feld den Fokus hat.
data-filledVorhanden, solange das Feld Text enthält.
data-invalidVorhanden, wenn ungültig.
data-disabledVorhanden, wenn deaktiviert.
data-readonlyVorhanden, wenn schreibgeschützt.
data-requiredVorhanden, wenn ein Passwort Pflicht ist.

Der Rahmen um Feld und Schalter. Er zeichnet den Fokusring, wenn das Feld fokussiert ist.

PropTypStandard
id
string–
placeholder
string–
render
ReactElement | (props, state) => ReactElement–
AttributBeschreibung
data-slot="password-input-control"Spricht das native Input in CSS an.
data-visibleVorhanden, solange es ein Textfeld ist.
PropTypStandard
aria-label
string"Show password"
childrenStandardmäßig ein Auge, das in ein geschlossenes Auge überblendet.
ReactNode | (state) => ReactNode–
render
ReactElement | (props, state) => ReactElement–
AttributBeschreibung
data-slot="password-input-toggle"Den Umschalter in CSS ansprechen.
aria-pressedtrue, solange das Passwort sichtbar ist.
PropTypStandard
children
ReactNode"Caps Lock is on"
AttributBeschreibung
data-slot="password-input-capslock"Spricht den Hinweis in CSS an.
data-activeVorhanden, solange die Feststelltaste aktiv ist.
PropTypStandard
getStrengthBewertet das Passwort. Der Standard gewichtet Länge, Zeichenvielfalt, Wiederholungen und gängige Passwörter. Für echte Richtlinien nimm einen stärkeren Schätzer wie zxcvbn.
(password: string) => 0 | 1 | 2 | 3 | 4scorePassword
labels
[string, string, string, string, string]["", "Weak", "Fair", "Good", "Strong"]
label
string"Password strength"
AttributBeschreibung
data-slot="password-input-strength"Spricht die Anzeige in CSS an.
data-scoreDie Punktzahl, von 0 für leer bis 4.