@passcore/vanilla
Vanilla JavaScript
@passcore/vanilla needs no framework and no jQuery: call createPasswordMeter() on an input, or write a <passcore-meter> element where you want the meter.
pnpm add @passcore/vanilla
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
With hideUntilFocus the meter stays hidden until the field gets focus, and hides again on blur while the field is empty.
import { createPasswordMeter } from '@passcore/vanilla'
import '@passcore/vanilla/styles.css'
createPasswordMeter('#default-password', { hideUntilFocus: true })
Requirements checklist
The classic signup form: every condition, red or green as people type. The onScore callback (or the bubbling passcore:score event) brings the full result, and result.rules lists every enabled rule, in order, with its id, passed and params. Typing a username calls refresh(), so the personal details rule follows it. Every item stays grey until something is typed: an empty password is “not common” too, and saying so in green would mislead.
Requirements
- At least 8 characters , met , not met
- Not a common password , met , not met
- No personal details, like your username , met , not met
- A lowercase letter , met , not met
- An uppercase letter , met , not met
- A number , met , not met
- A symbol , met , not met
7 requirements to meet
// the list: <li data-rule="minLength" data-state="idle">At least 8 characters</li>, and so on
const input = document.querySelector('#signup-password')
const summary = document.querySelector('#checklist-summary')
function check(result) {
const typed = input.value !== ''
for (const rule of result.rules) {
const item = document.querySelector(`#checklist [data-rule="${rule.id}"]`)
item.dataset.state = !typed ? 'idle' : rule.passed ? 'met' : 'unmet'
}
// a live region: only touch it when the count changes
const met = result.rules.filter((rule) => rule.passed).length
const text = typed ? `${met} of ${result.rules.length} requirements met` : `${result.rules.length} requirements to meet`
if (summary.textContent !== text) {
summary.textContent = text
}
}
const meter = createPasswordMeter(input, {
userInputs: ['#signup-username'],
rules: { minLength: 8, lowercase: 1, uppercase: 1, numbers: 1, symbols: 1 },
onScore: (percent, result) => check(result),
})
// or: input.addEventListener('passcore:score', (event) => check(event.detail.result))
// the username is read on each update: refresh when it changes
document.querySelector('#signup-username').addEventListener('input', () => meter.refresh())
Always visible, with the percent
Without hideUntilFocus the meter is shown from the start, and showPercent adds the number.
createPasswordMeter('#always-password', { showPercent: true })
Linked to a username
With userInputs the password can’t contain the username. Strings are selectors, read on every keystroke; functions work too.
createPasswordMeter('#linked-password', {
userInputs: ['#username'],
showPercent: true,
})
Bundled translations
The bundled files go in translations, locale picks the plural forms and label names the meter for screen readers. The options are read once, so a new language means a new meter.
import en from '@passcore/vanilla/locales/en.json'
import es from '@passcore/vanilla/locales/es.json'
import ca from '@passcore/vanilla/locales/ca.json'
const locales = { en, es, ca }
// the options are read once: create the meter again for another language
meter?.destroy()
meter = createPasswordMeter('#translations-password', {
translations: locales[lang],
locale: lang,
label: labels[lang],
showPercent: true,
})
Translations with i18next
translate takes the t of an i18next instance, here the playground’s own. Texts are translated on every update, so refresh() is all a language change needs.
import i18next from 'i18next'
import es from '@passcore/vanilla/locales/es.json'
import ca from '@passcore/vanilla/locales/ca.json'
i18next.addResourceBundle('es', 'passcore', es)
i18next.addResourceBundle('ca', 'passcore', ca)
const meter = createPasswordMeter('#i18next-password', {
translate: (key, params) => i18next.t(key, { ns: 'passcore', ...params }),
showPercent: true,
})
// the texts are translated on every update: refresh them
i18next.on('languageChanged', () => meter.refresh())
Callbacks and events
The onScore callback and the bubbling passcore:score event both get the percent on every update. Here they drive the Send button and the score.
const input = document.querySelector('#events-password')
const send = document.querySelector('#send')
createPasswordMeter(input, {
onScore: (percent) => {
send.disabled = percent <= 75
},
})
input.addEventListener('passcore:score', (event) => {
document.querySelector('#events-score').textContent = `${event.detail.percent}%`
})
Custom container
The meter goes right after the input by default. container appends it elsewhere: here, below the whole input group.
createPasswordMeter('#group-password', { container: '#group-field' })
The <passcore-meter> element
<passcore-meter> renders the meter where you write it. Attributes cover the simple options: min-length, show-percent, user-inputs, locale, label and a few more.
<script type="module">
import '@passcore/vanilla/element'
</script>
<input type="text" id="element-username">
<input type="password" id="element-password">
<passcore-meter
for="element-password"
min-length="10"
show-percent
user-inputs="#element-username"
></passcore-meter>
Element options from script
What attributes can’t hold (translations, rules, callbacks) goes in the options property, merged over the attributes. This one also asks for a number and a symbol.
const element = document.querySelector('passcore-meter[for="options-password"]')
// merged over the attributes: show-percent still applies
element.options = {
translations: locales[lang],
locale: lang,
rules: { numbers: 1, symbols: 1 },
}
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.