PasswordInput

A password field with a visibility toggle.

<script setup lang="ts">
  import { PasswordInput } from '@hina-ui/vue'
</script>

<template>
  <PasswordInput
    model-value="hoshimi-shion"
    aria-label="Password"
    autocomplete="current-password"
    class="w-64"
  />
</template>
vue

Usage

import { PasswordInput } from '@hina-ui/vue'
ts

The field ends with a button that switches between masked and plain text, with v-model bound to the text. Attributes it does not declare land on the input; pass autocomplete, using current-password for sign-in and new-password for sign-up and password changes.

<script setup lang="ts">
  import { ref } from 'vue'
  import { PasswordInput } from '@hina-ui/vue'

  const password = ref('')
</script>

<template>
  <PasswordInput
    v-model="password"
    aria-label="Password"
    placeholder="Password"
    autocomplete="current-password"
    class="w-64"
  />
</template>
vue

Examples

Visibility

visible controls whether the text is shown in plain, and supports v-model:visible. Two fields sharing one value toggle together.

<script setup lang="ts">
  import { ref } from 'vue'
  import { PasswordInput, Stack } from '@hina-ui/vue'

  const password = ref('')
  const confirm = ref('')
  const visible = ref(false)
</script>

<template>
  <Stack class="w-64">
    <PasswordInput
      v-model="password"
      v-model:visible="visible"
      aria-label="New password"
      placeholder="New password"
      autocomplete="new-password"
    />
    <PasswordInput
      v-model="confirm"
      v-model:visible="visible"
      aria-label="Confirm password"
      placeholder="Confirm password"
      autocomplete="new-password"
    />
  </Stack>
</template>
vue

Sizes

Three sizes, matching the input.

<script setup lang="ts">
  import { PasswordInput, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-64">
    <PasswordInput size="sm" model-value="hoshimi" aria-label="Small" />
    <PasswordInput size="md" model-value="hoshimi" aria-label="Medium" />
    <PasswordInput size="lg" model-value="hoshimi" aria-label="Large" />
  </Stack>
</template>
vue

Variants

primary has a border, background and shadow; secondary uses a tinted background; bare is transparent, without a border, shadow, hover fill or container focus ring. Size and padding keep their existing settings.

bare preserves the disabled state and aria-invalid; FormField can display the error message. Action buttons keep their own hover and keyboard focus feedback.

<script setup lang="ts">
  import { PasswordInput, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-full max-w-xs">
    <PasswordInput variant="primary" aria-label="primary" model-value="hoshimi" />
    <PasswordInput variant="secondary" aria-label="secondary" model-value="hoshimi" />
    <PasswordInput variant="bare" aria-label="bare" model-value="hoshimi" />
  </Stack>
</template>
vue

States

invalid marks a failed validation and disabled prevents editing.

<script setup lang="ts">
  import { PasswordInput, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-64">
    <PasswordInput invalid model-value="123456" aria-label="Invalid" />
    <PasswordInput disabled model-value="hoshimi" aria-label="Disabled" />
  </Stack>
</template>
vue

In a form

Inside a FormField the label points at the input, and the description and error message are rendered by the field; validation rules and submission belong to the Form. A rule that spans fields, such as the two entries matching, goes on the object and names the field the error lands on.

At least 8 characters with a digit

<script setup lang="ts">
  import { reactive, ref } from 'vue'
  import * as v from 'valibot'
  import { Button, Form, FormField, PasswordInput, Text } from '@hina-ui/vue'

  const schema = v.pipe(
    v.object({
      password: v.pipe(
        v.string('Enter a password'),
        v.minLength(8, 'At least 8 characters'),
        v.regex(/[0-9]/, 'Include at least one digit'),
      ),
      confirm: v.string('Enter the password again'),
    }),
    v.forward(
      v.check(input => input.password === input.confirm, 'The passwords do not match'),
      ['confirm'],
    ),
  )

  const values = reactive({ password: '', confirm: '' })
  const saved = ref(false)

  async function save() {
    await new Promise(resolve => setTimeout(resolve, 600))
    saved.value = true
  }
</script>

<template>
  <Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
    <FormField
      name="password"
      label="New password"
      description="At least 8 characters with a digit"
      required
    >
      <PasswordInput v-model="values.password" />
    </FormField>
    <FormField name="confirm" label="Confirm password" required>
      <PasswordInput v-model="values.confirm" />
    </FormField>
    <Button type="submit" :loading="submitting" class="self-start">Change password</Button>
    <Text v-if="saved" tone="muted" size="sm">Password changed.</Text>
  </Form>
</template>
vue

Behaviour

  • Clicking the toggle does not blur the text area, so typing can continue after switching.
  • The toggle only changes how the text is displayed, not its value.
  • Hover, focus, invalid and disabled behave the same as Input.

Accessibility

  • The toggle is reachable with Tab; its name switches between “Show password” and “Hide password” with the state.
  • Pair it with a label element or an aria-label for its name. invalid also sets aria-invalid.

API

Props

Prop
Type
Default
Description
modelValue
string
—
The text
visible
boolean
false
Whether the text is shown in plain; v-model:visible
variant
'primary' | 'secondary' | 'bare'
'primary'
Variant
size
'sm' | 'md' | 'lg'
'md'
Size
invalid
boolean
false
Whether validation failed
disabled
boolean
false
Whether the field is disabled
class
string
—
Classes appended to the root element
Event
Payload
Description
update:modelValue
value: string
The text changed
update:visible
visible: boolean
The visibility changed