@passcore/vue

Vue

@passcore/vue brings a <PasswordStrengthMeter> component and the usePasswordStrength() composable, for Vue 3.5 and later.

pnpm add @passcore/vue
Read the README

Every meter on this page speaks your language. The playground gives each one the texts of the language you pick at the top, and its accessible name. To keep the snippets short, only the translation demos show how; the show and hide button in the fields belongs to the playground, not to the package.

Default

The component gets the password as a prop, so it works with any v-model. It is always shown; id links it to the field.

Vue
<script setup>
import { ref } from 'vue'
import { PasswordStrengthMeter } from '@passcore/vue'
import '@passcore/vue/styles.css'

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

<template>
  <input id="password" v-model="password" type="password" aria-describedby="password-strength">
  <PasswordStrengthMeter id="password-strength" :password="password" />
</template>

Requirements checklist

The classic signup form, built with your own markup: the usePasswordStrength() composable returns the result as a ref, and result.rules lists every enabled rule, in order, with its id, passed and params. The component draws the bar next to it. Every item stays grey until something is typed: an empty password is “not common” too, and saying so in green would mislead.

Vue
<script setup>
import { computed, ref } from 'vue'
import { PasswordStrengthMeter, usePasswordStrength } from '@passcore/vue'

const rules = { minLength: 8, lowercase: 1, uppercase: 1, numbers: 1, symbols: 1 }
const username = ref('')
const password = ref('')

const { result } = usePasswordStrength(password, () => ({ userInputs: [username.value], rules }))
const typed = computed(() => password.value !== '')
const met = computed(() => result.value.rules.filter((rule) => rule.passed).length)
</script>

<template>
  <PasswordStrengthMeter :password="password" :user-inputs="[username]" :rules="rules" />
  <ul>
    <li v-for="rule in result.rules" :key="rule.id" :data-state="!typed ? 'idle' : rule.passed ? 'met' : 'unmet'">
      {{ t(`requirements.rule.${rule.id}`, { count: rule.params.min }) }}
    </li>
  </ul>
  <p aria-live="polite">
    {{ typed ? `${met} of ${result.rules.length} requirements met` : `${result.rules.length} requirements to meet` }}
  </p>
</template>

With the percent

show-percent adds the number next to the bar.

Vue
<PasswordStrengthMeter id="password-strength" :password="password" show-percent />

Linked to a username

With user-inputs the password can’t contain the username. Pass the current values, straight from your refs.

Vue
<input v-model="username" type="text" autocomplete="username">
<PasswordStrengthMeter
  id="password-strength"
  :password="password"
  :user-inputs="[username]"
  show-percent
/>

Bundled translations

The bundled files go in translations, locale picks the plural forms and label names the meter for screen readers. Switch the language at the top: new props, new texts.

Vue
<script setup>
import en from '@passcore/vue/locales/en.json'
import es from '@passcore/vue/locales/es.json'
import ca from '@passcore/vue/locales/ca.json'

const locales = { en, es, ca }
</script>

<template>
  <PasswordStrengthMeter
    :password="password"
    :translations="locales[lang]"
    :locale="lang"
    :label="labels[lang]"
    show-percent
  />
</template>

Translations with i18next

translate takes the t of an i18next instance, here the playground’s own. The function never changes, so locale tells the component when to translate again.

Vue
<script setup>
import i18next from 'i18next'
import es from '@passcore/vue/locales/es.json'
import ca from '@passcore/vue/locales/ca.json'

i18next.addResourceBundle('es', 'passcore', es)
i18next.addResourceBundle('ca', 'passcore', ca)

const translate = (key, params) => i18next.t(key, { ns: 'passcore', ...params })
</script>

<template>
  <!-- locale tells the meter to translate again -->
  <PasswordStrengthMeter :password="password" :translate="translate" :locale="lang" :label="labels[lang]" show-percent />
</template>

Callbacks and events

The score event brings the percent whenever the result changes (never on mount). Here it drives the score and the Send button.

Vue
<script setup>
const percent = ref(0)
</script>

<template>
  <PasswordStrengthMeter :password="password" @score="(value) => (percent = value)" />
  <output>{{ percent }}%</output>
  <button type="submit" :disabled="percent <= 75">Send</button>
</template>

Input group

The meter is just another element: put it after the input group and it spans the whole group.

Vue
<div class="form-group">
  <label for="password">Password</label>
  <div class="input-group">
    <span class="input-group__addon">…</span>
    <input id="password" v-model="password" type="password">
  </div>
  <PasswordStrengthMeter :password="password" />
</div>

Theming

Colours, track and height are CSS custom properties: --pass-color-very-weak to --pass-color-strong, --pass-track and --pass-height. Play with them here; the CSS below follows along.

  • Very weak
  • Weak
  • Fair
  • Good
  • Strong
Custom properties
Presets
CSS
.signup {
  --pass-height: 6px;
  --pass-track: #e4e2db;
  --pass-color-very-weak: #d92d20;
  --pass-color-weak: #e06a0f;
  --pass-color-fair: #c99400;
  --pass-color-good: #5e9e0f;
  --pass-color-strong: #16884a;
}

The controls only change this preview, so the other demos keep their look. Set the properties on :root to theme a whole site.