@passcore/svelte
Svelte
@passcore/svelte brings a <PasswordStrengthMeter> component and passwordStrength(), a rune-based helper, for Svelte 5.
pnpm add @passcore/svelte
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 bind:value on your input is all it takes. It is always shown; id links it to the field.
<script>
import { PasswordStrengthMeter } from '@passcore/svelte'
import '@passcore/svelte/styles.css'
let password = $state('')
</script>
<input id="password" type="password" bind:value={password} aria-describedby="password-strength" />
<PasswordStrengthMeter id="password-strength" {password} />
Requirements checklist
The classic signup form, built with your own markup: passwordStrength() returns the result as a getter, 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.
<script>
import { PasswordStrengthMeter, passwordStrength } from '@passcore/svelte'
const rules = { minLength: 8, lowercase: 1, uppercase: 1, numbers: 1, symbols: 1 }
let username = $state('')
let password = $state('')
const strength = passwordStrength(() => password, () => ({ userInputs: [username], rules }))
const typed = $derived(password !== '')
const met = $derived(strength.result.rules.filter((rule) => rule.passed).length)
</script>
<PasswordStrengthMeter {password} userInputs={[username]} {rules} />
<ul>
{#each strength.result.rules as rule (rule.id)}
<li data-state={!typed ? 'idle' : rule.passed ? 'met' : 'unmet'}>
{t(`requirements.rule.${rule.id}`, { count: rule.params.min })}
</li>
{/each}
</ul>
<p aria-live="polite">
{typed ? `${met} of ${strength.result.rules.length} requirements met` : `${strength.result.rules.length} requirements to meet`}
</p>
With the percent
showPercent adds the number next to the bar.
<PasswordStrengthMeter id="password-strength" {password} showPercent />
Linked to a username
With userInputs the password can’t contain the username. Pass the current values, straight from your state.
<script>
let username = $state('')
</script>
<input type="text" autocomplete="username" bind:value={username} />
<PasswordStrengthMeter id="password-strength" {password} userInputs={[username]} showPercent />
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.
<script>
import en from '@passcore/svelte/locales/en.json'
import es from '@passcore/svelte/locales/es.json'
import ca from '@passcore/svelte/locales/ca.json'
const locales = { en, es, ca }
</script>
<PasswordStrengthMeter
{password}
translations={locales[lang]}
locale={lang}
label={labels[lang]}
showPercent
/>
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.
<script>
import i18next from 'i18next'
import es from '@passcore/svelte/locales/es.json'
import ca from '@passcore/svelte/locales/ca.json'
i18next.addResourceBundle('es', 'passcore', es)
i18next.addResourceBundle('ca', 'passcore', ca)
const translate = (key, params) => i18next.t(key, { ns: 'passcore', ...params })
</script>
<!-- locale tells the meter to translate again -->
<PasswordStrengthMeter {password} {translate} locale={lang} label={labels[lang]} showPercent />
Callbacks and events
onscore gets the percent whenever the result changes (never on mount). Here it drives the score and the Send button.
<script>
let percent = $state(0)
</script>
<PasswordStrengthMeter {password} onscore={(value) => (percent = value)} />
<output>{percent}%</output>
<button type="submit" disabled={percent <= 75}>Send</button>
Input group
The meter is just another element: put it after the input group and it spans the whole group.
<div class="form-group">
<label for="password">Password</label>
<div class="input-group">
<span class="input-group__addon">…</span>
<input id="password" type="password" bind:value={password} />
</div>
<PasswordStrengthMeter {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
.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.