Password input - headless
createPasswordInput drives SvPasswordInput and a custom reveal field + strength meter.
A live, editable Svelte 5 component example from the SvGrid gallery (Headless Editors). Read the SvGrid UI component docs for the full API.
About this example
The headless password input core of the SvGrid UI kit in Svelte 5. SvPasswordInput is a styled renderer over createPasswordInput, which owns the reveal toggle and the four-level strength heuristic, exposed as inputProps and toggleProps. The same core drives the styled component and a custom reveal field plus strength meter, both bound to one value.
Headless password input - <SvPasswordInput> is just a styled renderer over the createPasswordInput core: the reveal toggle + a 4-level strength heuristic, exposed as **prop-getters** you spread onto YOUR OWN markup. The same core drives the styled component AND a custom render, both bound to one value.
Imports, features and API used
Imports: @svgrid/grid
Frequently asked questions
What does toggleProps wire?
A button that flips the input between password and text type, with aria-pressed and an accessible label from the messages; inputProps reflects the current type.
How is strength scored?
From length and the presence of lower case, upper case, digits and symbols, mapped to four levels the core exposes as a number and a label for any meter design.
Is the value ever exposed unmasked?
Only through the bound value your app already holds; the reveal toggle changes how the input element displays it, nothing else.
Related documentation
Related articles
- SvGrid vs TanStack Table - A Deep Dive - A concrete architectural comparison of SvGrid and TanStack Table's Svelte adapter - how each handles reactivity, rendering, and feature composition, with code that shows exactly where they diverge.
- SvGrid vs svelte-headless-table - A practical comparison of SvGrid and svelte-headless-table covering reactivity model, rendering approach, feature scope, and when each is the right choice for a Svelte 5 project.
- SvGrid UI Components Tips and Tricks: Svelte 5 Buttons, Inputs, Tree, Forms - Practical tips for the SvGrid UI component kit - SvButton states, a Cmd+K command palette, a virtualized searchable tree, schema-driven forms, a parsing date/time field, and the shared field contract - each with a code snippet.
Source code (270-headless-passwordinput.svelte)
<script lang="ts">
/**
* Headless password input - <SvPasswordInput> is just a styled renderer over
* the `createPasswordInput` core: the reveal toggle + a 4-level strength
* heuristic, exposed as **prop-getters** you spread onto YOUR OWN markup. The
* same core drives the styled component AND a custom render, both bound to one
* value.
*/
import { SvPasswordInput, createPasswordInput } from '@svgrid/grid'
let value = $state('')
// The headless core - identical behavior, our own DOM.
const pw = createPasswordInput({
value: () => value,
onChange: (v) => (value = v),
})
</script>
<div class="wrap">
<header>
<h2>Headless password input</h2>
<p>
Every editor ships a headless core + a styled component - the same split as the grid
(<code>createSvGrid</code> / <code><SvGrid></code>). Here <code>createPasswordInput</code>
drives both renders below; the reveal toggle and strength score come from the core, the markup is yours.
</p>
</header>
<div class="cols">
<section>
<h3>Styled <code><SvPasswordInput></code></h3>
<SvPasswordInput {value} onChange={(v) => (value = v)} showStrength ariaLabel="Password" />
</section>
<section>
<h3>Your markup, same core</h3>
<div class="field">
<input class="input" {...pw.inputProps()} placeholder="Passphrase" />
<button class="reveal" {...pw.toggleProps()}>{pw.revealed ? 'Hide' : 'Show'}</button>
</div>
<div class="meter">
{#each Array(4) as _, i (i)}
<span class="bar lvl{pw.strength}" class:on={i < pw.strength}></span>
{/each}
<span class="label lvl{pw.strength}">{pw.strengthLabel}</span>
</div>
<p class="hint">Type - the reveal toggle and 0-4 strength score come from <code>createPasswordInput</code>.</p>
</section>
</div>
<aside class="readout">
<h3>Value</h3>
{#if value}
<div class="tags"><span class="tag">{pw.revealed ? value : '•'.repeat(value.length)}</span><span class="tag">{pw.strengthLabel}</span></div>
{:else}
<p class="empty">Empty</p>
{/if}
</aside>
</div>
<style>
.wrap { padding: 20px; max-width: 900px; display: flex; flex-direction: column; gap: 18px; }
header h2 { margin: 0 0 4px; font-size: 20px; font-weight: 700; }
header p { margin: 0; color: var(--sg-muted, #64748b); font-size: 13.5px; line-height: 1.55; max-width: 680px; }
code { background: var(--sg-row-hover-bg, #eef2ff); padding: 1px 5px; border-radius: 5px; font-size: 12px; }
.cols { display: flex; gap: 32px; flex-wrap: wrap; align-items: flex-start; }
section h3 { margin: 0 0 10px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--sg-muted, #64748b); }
/* Fully custom password render - nothing shared with SvPasswordInput but the core. */
.field {
display: inline-flex; align-items: stretch; width: 240px;
border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px; background: var(--sg-input-bg, #fff); overflow: hidden;
}
.field:focus-within { border-color: var(--sg-accent, #4f46e5); box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-accent, #4f46e5) 30%, transparent); }
.input { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: var(--sg-fg, #0f172a); font: inherit; font-size: 14px; padding: 0 12px; height: 38px; }
.reveal { border: 0; border-left: 1px solid var(--sg-border, #e2e8f0); background: var(--sg-row-hover-bg, #f1f5f9); color: var(--sg-muted, #64748b); font-size: 12px; font-weight: 600; padding: 0 12px; cursor: pointer; }
.reveal:hover { color: var(--sg-accent, #4f46e5); }
.meter { display: flex; align-items: center; gap: 4px; margin-top: 10px; width: 240px; }
.bar { flex: 1; height: 5px; border-radius: 3px; background: var(--sg-border, #e2e8f0); transition: background 0.15s; }
.bar.on.lvl1 { background: #dc2626; }
.bar.on.lvl2 { background: #f59e0b; }
.bar.on.lvl3 { background: #eab308; }
.bar.on.lvl4 { background: #16a34a; }
.label { margin-left: 8px; font-size: 11px; font-weight: 700; color: var(--sg-muted, #94a3b8); min-width: 46px; }
.label.lvl1 { color: #dc2626; }
.label.lvl4 { color: #16a34a; }
.hint { margin: 10px 0 0; font-size: 12px; color: var(--sg-muted, #94a3b8); line-height: 1.5; }
.readout h3 { margin: 0 0 8px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--sg-muted, #64748b); }
.tags { display: flex; gap: 6px; flex-wrap: wrap; }
.tag { padding: 3px 10px; border-radius: 6px; font-size: 13px; background: var(--sg-row-hover-bg, #f1f5f9); font-variant-numeric: tabular-nums; }
.empty { color: var(--sg-muted, #94a3b8); font-style: italic; margin: 0; }
</style>More Headless Editors examples
- Headless editors - Headless-first, like the grid: createListbox is the state machine behind SvListBox (roving focus, single/multi selection, keyboard, ARIA) exposed as prop-getters you spread onto YOUR own markup. One core drives both the styled SvListBox and a custom chip-cloud render, bound to one value.
- Combobox - headless - createCombobox drives the styled SvComboBox and a custom filter-input render, both bound to one value with a readout.
- Dropdown list - headless - createDropdownList drives SvDropDownList and a custom trigger+menu render, sharing one value.
- Autocomplete - headless - createAutocomplete drives SvAutoComplete and a custom suggestion-list render over one text value.
- Tags input - headless - createTagsInput drives SvTagsInput and a custom chip-cloud render, both editing one string array.