feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< script lang = "ts" >
import {
PasswordField,
type PasswordFieldColor,
type PasswordFieldPurpose,
type PasswordFieldSize,
type PasswordFieldVariant
} from '$uix/eidos/components/password-field';
import { compileMorfo } from '$uix/morfo';
import { passwordFieldMorfo } from '@/uix/morfo/components/password-field';
import { getActiveUix } from '$active-uix';
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import { passwordStrength , SECS_PASSWORD_STRENGTH_MAX } from '$libs/secs';
// Number of visual segments — one per canonical strength step above zero.
const STRENGTH_SEGMENTS = Array.from(
{ length : SECS_PASSWORD_STRENGTH_MAX } ,
(_, i) => i
);
feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type TraceEntry = { event : string ; family : string ; intent? : string ; at : number } ;
const uix = getActiveUix();
let tab = $state< Tab > ('live');
let trace = $state< TraceEntry [ ] > ([]);
let stageRef = $state< HTMLElement | null > (null);
// soma props
let value = $state('');
let visible = $state(false);
let purpose = $state< PasswordFieldPurpose > ('current');
let placeholder = $state('Enter your password');
let maxLength = $state< number | undefined > (64);
let minLength = $state< number | undefined > (8);
let disabled = $state(false);
let readonly = $state(false);
let required = $state(true);
let invalid = $state(false);
let name = $state('password');
// eidos props
let size = $state< PasswordFieldSize > ('md');
let variant = $state< PasswordFieldVariant > ('surface');
let color = $state< PasswordFieldColor > ('primary');
// composition switches
let showVisibilityTrigger = $state(true);
let showCapsLockIndicator = $state(true);
let showStrengthMeter = $state(true);
let lastSubmit = $state< string | null > (null);
const compiled = compileMorfo(passwordFieldMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
const events = $derived([...compiled.actions.byName.values()]);
const sizes: PasswordFieldSize[] = ['xs', 'sm', 'md', 'lg', 'xl'];
const variants: PasswordFieldVariant[] = ['surface', 'outline', 'ghost'];
const colors: PasswordFieldColor[] = [
'primary',
'secondary',
'neutral',
'affirm',
'fulfill',
'risk',
'threat',
'loss'
];
const purposes: PasswordFieldPurpose[] = ['current', 'new'];
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// Used for the trace pill on the live stage. The stage's
// `< PasswordField.StrengthMeter / > ` reads its score from the provider
// automatically (no `strength` prop), so this is purely cosmetic.
const strength = $derived(passwordStrength(value).score);
feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
$effect(() => {
const el = stageRef;
if (!el) return;
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.attributeName !== 'data-event') continue;
const target = m.target as Element;
const ev = target.getAttribute('data-event');
if (!ev) continue;
trace = [
{
event: ev,
family: target.getAttribute('data-event-family') ?? '—',
intent: target.getAttribute('data-event-intent') ?? undefined,
at: Date.now()
},
...trace
].slice(0, 6);
}
});
obs.observe(el, { attributes : true , subtree : true , attributeFilter : [ 'data-event' ] } );
return () => obs.disconnect();
});
function fmtTime(at: number): string {
const d = new Date(at);
return `${ String ( d . getSeconds ()). padStart ( 2 , '0' )} .${ String ( d . getMilliseconds ()). padStart ( 3 , '0' )} `;
}
const somaSnippet = $derived(
[
"< script lang = 'ts' > " ,
" import * as PasswordField from '$soma/components/password-field';",
' let value = $state(\'\');',
' let visible = $state(false);',
'< /' + 'script>',
'',
'< PasswordField.Provider ' ,
' bind:value',
' bind:visible',
purpose !== 'current' && ` purpose="${ purpose } "`,
minLength !== undefined && ` minLength={ $ { minLength }} `,
maxLength !== undefined && ` maxLength={ $ { maxLength }} `,
required & & ' required',
'>',
' < PasswordField.Input placeholder = "…" / > ',
showVisibilityTrigger & & ' < PasswordField.VisibilityTrigger / > ',
showCapsLockIndicator & & ' < PasswordField.CapsLockIndicator / > ',
'< / PasswordField.Provider > '
]
.filter(Boolean)
.join('\n')
);
const eidosSnippet = $derived(
[
"< script lang = 'ts' > " ,
" import { PasswordField } from '$uix/eidos/components/password-field';",
' let value = $state(\'\');',
' let visible = $state(false);',
'< /' + 'script>',
'',
'< PasswordField ' ,
' bind:value',
' bind:visible',
purpose !== 'current' && ` purpose="${ purpose } "`,
minLength !== undefined && ` minLength={ $ { minLength }} `,
maxLength !== undefined && ` maxLength={ $ { maxLength }} `,
required & & ' required',
size !== 'md' && ` size="${ size } "`,
variant !== 'surface' && ` variant="${ variant } "`,
color !== 'primary' && ` color="${ color } "`,
'>',
' < PasswordField.Input placeholder = "…" / > ',
showVisibilityTrigger & & ' < PasswordField.VisibilityTrigger / > ',
showCapsLockIndicator & & ' < PasswordField.CapsLockIndicator / > ',
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
showStrengthMeter & & ' < PasswordField.StrengthMeter / > ',
feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
'< / PasswordField > '
]
.filter(Boolean)
.join('\n')
);
< / script >
< div data-uix-canvas-inner >
< header >
< div data-uix-eyebrow > Forms · PasswordField< / div >
< h1 data-uix-page-title > PasswordField< / h1 >
< p data-uix-page-lede >
Single-line password input with masked value, visibility toggle, CapsLock awareness, and an
optional composable strength meter. Anatomy follows Ark UI; CapsLockIndicator fills a
universal gap (10/10 surveyed libraries warn nothing when Caps Lock is on while typing).
Soma owns value, masking, autocomplete (< code > current-password< / code > /< code > new-password< / code > )
and Caps Lock detection.
< / p >
< div data-uix-page-meta >
< span data-uix-meta-pill >
< span data-uix-meta-key > parts</ span > { compiled . parts . order . length }
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > events</ span > { events . length }
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > keyboard< / span > 1
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > sizes</ span > { sizes . length }
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > apg< / span >
< a href = { passwordFieldMorfo . apg } target="_blank" rel = "noreferrer" > textbox</ a >
< / span >
< / div >
< / header >
<!-- Live preview always rendered (between header and tabs). -->
< div data-uix-stage >
< div data-uix-stage-area bind:this = { stageRef } style="padding: var ( --uix-space-4 );" >
< div style = "inline-size: 100%; max-inline-size: 24rem; margin-inline: auto;" >
feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< PasswordField
bind:value
bind:visible
{ purpose }
{ placeholder }
{ maxLength }
{ minLength }
{ disabled }
{ readonly }
{ required }
{ invalid }
{ name }
{ size }
{ variant }
{ color }
onSubmit={( v ) => ( lastSubmit = v )}
>
< PasswordField.Input / >
{ #if showCapsLockIndicator }
< PasswordField.CapsLockIndicator / >
{ /if }
{ #if showVisibilityTrigger }
< PasswordField.VisibilityTrigger / >
{ /if }
{ #if showStrengthMeter }
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< PasswordField.StrengthMeter / >
feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
{ /if }
< / PasswordField >
< / div >
< / div >
< div data-uix-stage-trace >
< span data-uix-stage-trace-key > trace< / span >
{ #if trace . length === 0 }
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< span > press Caps Lock, click the eye, or press Enter to see events< / span >
feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
{ : else }
{ #each trace . slice ( 0 , 3 ) as entry }
< span >
< span data-uix-stage-trace-event > { entry . event } </ span >
· { entry . family }{ entry . intent ? ' · ' + entry . intent : '' }
< / span >
< span style = "color: var(--uix-text-faint)" > { fmtTime ( entry . at )} </ span >
{ /each }
{ /if }
< span style = "margin-inline-start: auto;" >
< span data-uix-stage-trace-key > length< / span >
{ value . length } · < span data-uix-stage-trace-key > strength</ span > { strength } /4
< / span >
< / div >
< / div >
< div data-uix-tabs role = "tablist" >
< button data-uix-tab data-active = { tab === 'live' } onclick= {() => ( tab = 'live' )} > Live</ button >
< button data-uix-tab data-active = { tab === 'api' } onclick= {() => ( tab = 'api' )} >
API < span data-uix-tab-count > 14< / span >
< / button >
< button data-uix-tab data-active = { tab === 'morfo' } onclick= {() => ( tab = 'morfo' )} >
< span data-uix-layer-badge = "morfo" > morfo< / span >
< span data-uix-tab-count > { partsList . length } p · { events . length } e</ span >
< / button >
< button data-uix-tab data-active = { tab === 'sema' } onclick= {() => ( tab = 'sema' )} >
< span data-uix-layer-badge = "sema" > sema< / span >
< span data-uix-tab-count > { events . length } </ span >
< / button >
< button data-uix-tab data-active = { tab === 'recipe' } onclick= {() => ( tab = 'recipe' )} > Recipe< /button
>
< button data-uix-tab data-active = { tab === 'a11y' } onclick= {() => ( tab = 'a11y' )} > A11y</ button >
< / div >
{ #if tab === 'live' }
< section data-uix-section >
< h2 data-uix-section-title > Controls< / h2 >
< p data-uix-section-desc >
< span data-uix-layer-badge = "soma" > soma< / span > owns value, masking and CapsLock detection.
< span data-uix-layer-badge = "eidos" > eidos< / span > owns size, variant, color and icon
rendering.
< / p >
< div data-uix-subsection-head >
< span data-uix-layer-badge = "soma" > soma< / span > props · value & behaviour
< / div >
< div data-uix-controls >
< label data-uix-control >
< span data-uix-control-label
>value
< span data-uix-control-hint > { value . length } chars</ span >
< / span >
< input type = "text" bind:value placeholder = "(empty)" / >
< / label >
< label data-uix-control >
< span data-uix-control-label > visible< / span >
< span data-uix-switch >
< input type = "checkbox" bind:checked = { visible } / >
< span data-uix-switch-label > { visible ? 'plaintext' : 'masked' } </ span >
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > purpose< / span >
< span data-uix-chips role = "radiogroup" >
{ #each purposes as p ( p )}
< button data-uix-chip data-active = { purpose === p } onclick= {() => ( purpose = p )}
>{ p } < /button
>
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > placeholder< / span >
< input type = "text" bind:value = { placeholder } / >
< / label >
< label data-uix-control >
< span data-uix-control-label > minLength< / span >
< input
type="number"
min="0"
value={ minLength ?? '' }
oninput={( e ) => {
const v = e.currentTarget.value;
minLength = v === '' ? undefined : Number(v);
}}
/>
< / label >
< label data-uix-control >
< span data-uix-control-label > maxLength< / span >
< input
type="number"
min="1"
value={ maxLength ?? '' }
oninput={( e ) => {
const v = e.currentTarget.value;
maxLength = v === '' ? undefined : Number(v);
}}
/>
< / label >
< label data-uix-control >
< span data-uix-control-label > name< / span >
< input type = "text" bind:value = { name } / >
< / label >
< / div >
< div data-uix-subsection-head >
< span data-uix-layer-badge = "soma" > soma< / span > props · flags
< / div >
< div data-uix-controls >
{ #each [ 'disabled' , 'readonly' , 'required' , 'invalid' ] as flag }
< label data-uix-control >
< span data-uix-control-label > { flag } </ span >
< span data-uix-switch >
{ #if flag === 'disabled' }
< input type = "checkbox" bind:checked = { disabled } / >
< span data-uix-switch-label > { disabled ? 'on' : 'off' } </ span >
{ :else if flag === 'readonly' }
< input type = "checkbox" bind:checked = { readonly } / >
< span data-uix-switch-label > { readonly ? 'on' : 'off' } </ span >
{ :else if flag === 'required' }
< input type = "checkbox" bind:checked = { required } / >
< span data-uix-switch-label > { required ? 'on' : 'off' } </ span >
{ : else }
< input type = "checkbox" bind:checked = { invalid } / >
< span data-uix-switch-label > { invalid ? 'on' : 'off' } </ span >
{ /if }
< / span >
< / label >
{ /each }
< / div >
< div data-uix-subsection-head >
< span data-uix-layer-badge = "eidos" > eidos< / span > props · presentation
< / div >
< div data-uix-controls >
< label data-uix-control >
< span data-uix-control-label > size< / span >
< span data-uix-chips role = "radiogroup" >
{ #each sizes as s ( s )}
< button data-uix-chip data-active = { size === s } onclick= {() => ( size = s )} > { s } </ button >
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > variant< / span >
< span data-uix-chips role = "radiogroup" >
{ #each variants as v ( v )}
< button data-uix-chip data-active = { variant === v } onclick= {() => ( variant = v )}
>{ v } < /button
>
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > color< / span >
< span data-uix-chips role = "radiogroup" >
{ #each colors as c ( c )}
< button data-uix-chip data-active = { color === c } onclick= {() => ( color = c )}
>{ c } < /button
>
{ /each }
< / span >
< / label >
< / div >
< div data-uix-subsection-head > Composition · which parts to include< / div >
< div data-uix-controls >
< label data-uix-control >
< span data-uix-control-label > VisibilityTrigger< / span >
< span data-uix-switch >
< input type = "checkbox" bind:checked = { showVisibilityTrigger } / >
< span data-uix-switch-label > { showVisibilityTrigger ? 'on' : 'off' } </ span >
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > CapsLockIndicator< / span >
< span data-uix-switch >
< input type = "checkbox" bind:checked = { showCapsLockIndicator } / >
< span data-uix-switch-label > { showCapsLockIndicator ? 'on' : 'off' } </ span >
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > StrengthMeter< / span >
< span data-uix-switch >
< input type = "checkbox" bind:checked = { showStrengthMeter } / >
< span data-uix-switch-label > { showStrengthMeter ? 'on' : 'off' } </ span >
< / span >
< / label >
< / div >
{ #if lastSubmit !== null }
< div data-uix-subsection-head > Last submitted< / div >
< pre data-uix-snippet >< code > { lastSubmit } </ code ></ pre >
{ /if }
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
<!-- ── Code snippets per layer ─────────────────────────────────── -->
< div data-uix-code >
< div data-uix-code-head >
< span data-uix-layer-badge = "soma" > soma< / span >
< span > headless · ARIA + behavior only< / span >
< span data-uix-code-lang > svelte< / span >
< / div >
< pre >< code > { somaSnippet } </ code ></ pre >
< / div >
< div data-uix-code style = "margin-top: var(--uix-space-3);" >
< div data-uix-code-head >
< span data-uix-layer-badge = "eidos" > eidos< / span >
< span > visual · adds icons, chrome + strength meter< / span >
< span data-uix-code-lang > svelte< / span >
< / div >
< pre >< code > { eidosSnippet } </ code ></ pre >
< / div >
< / section >
{ /if }
{ #if tab === 'api' }
< section data-uix-section >
< h2 data-uix-section-title > API reference< / h2 >
< p data-uix-section-desc >
PasswordField composes through 5 parts. Root drives value + visibility + caps detection;
every visual part is optional (composition over visibility props).
< / p >
< div data-uix-subsection-head > Provider props< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Prop< / th > < th > Type< / th > < th > Notes< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > value< / td > < td class = "type" > string< / td > < td > Bindable. Default < code > ''< / code > .< / td > < / tr >
< tr > < td class = "name" > visible< / td > < td class = "type" > boolean< / td > < td > Bindable. Toggle plaintext / masked. Default < code > false< / code > .< / td > < / tr >
< tr >
< td class = "name" > purpose< / td >
< td class = "type" > 'current' | 'new'< / td >
< td >
Drives the native < code > autocomplete< / code > attribute. < code > 'current'< / code > →
< code > current-password< / code > (login). < code > 'new'< / code > →
< code > new-password< / code > (registration / change). Default < code > 'current'< / code > .
< / td >
< / tr >
< tr > < td class = "name" > minLength / maxLength< / td > < td class = "type" > number< / td > < td > Native constraints + mirrored ARIA. Used by HTML form validation.< / td > < / tr >
< tr > < td class = "name" > disabled / readonly / required / invalid< / td > < td class = "type" > boolean< / td > < td > OR-merged with parent < code > < Field> < / code > .< / td > < / tr >
< tr > < td class = "name" > placeholder< / td > < td class = "type" > string?< / td > < td > Native placeholder.< / td > < / tr >
< tr > < td class = "name" > name< / td > < td class = "type" > string?< / td > < td > Form submission name.< / td > < / tr >
< tr > < td class = "name" > aria-label< / td > < td class = "type" > string?< / td > < td > Override translated 'Password' default. Ignored if inside Field.Label.< / td > < / tr >
< tr > < td class = "name" > onValueChange< / td > < td class = "type" > (v: string) => void< / td > < td > Fires on every keystroke.< / td > < / tr >
< tr > < td class = "name" > onSubmit< / td > < td class = "type" > (v: string) => void< / td > < td > Fires on Enter.< / td > < / tr >
< tr > < td class = "name" > onVisibilityChange< / td > < td class = "type" > (visible: boolean) => void< / td > < td > Fires after the eye toggle.< / td > < / tr >
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > Eidos visual props< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Prop< / th > < th > Type< / th > < th > Default< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > size< / td > < td class = "type" > 'xs' | 'sm' | 'md' | 'lg' | 'xl'< / td > < td > < code > 'md'< / code > < / td > < / tr >
< tr > < td class = "name" > variant< / td > < td class = "type" > ControlVariant< / td > < td > < code > 'surface'< / code > < / td > < / tr >
< tr > < td class = "name" > color< / td > < td class = "type" > ColorRole< / td > < td > < code > 'primary'< / code > < / td > < / tr >
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > Reference comparison< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead >
< tr > < th > Library< / th > < th > Built-in toggle< / th > < th > CapsLock< / th > < th > Strength meter< / th > < / tr >
< / thead >
< tbody >
< tr > < td class = "name" > React Aria< / td > < td class = "default" > no< / td > < td class = "default" > no< / td > < td class = "default" > no< / td > < / tr >
< tr > < td class = "name" > Ark UI< / td > < td > yes< / td > < td class = "default" > no< / td > < td class = "default" > external< / td > < / tr >
< tr > < td class = "name" > Chakra v3< / td > < td > yes< / td > < td class = "default" > no< / td > < td > sibling cmp< / td > < / tr >
< tr > < td class = "name" > Mantine< / td > < td > yes< / td > < td class = "default" > no< / td > < td > compose< / td > < / tr >
< tr > < td class = "name" > PrimeReact< / td > < td > yes< / td > < td class = "default" > no< / td > < td > built-in popup< / td > < / tr >
< tr > < td class = "name" > Ant Design< / td > < td > yes (default)< / td > < td class = "default" > no< / td > < td class = "default" > no< / td > < / tr >
< tr > < td class = "name" > shadcn/ui< / td > < td class = "default" > no< / td > < td class = "default" > no< / td > < td class = "default" > no< / td > < / tr >
< tr > < td class = "name" > Material UI< / td > < td class = "default" > no< / td > < td class = "default" > no< / td > < td class = "default" > no< / td > < / tr >
< tr style = "background: color-mix(in srgb, var(--color-affirm-track) 30%, transparent);" >
< td class = "name" > < strong > UIX< / strong > < / td >
< td > < strong > yes< / strong > < / td >
< td > < strong > yes (universal gap)< / strong > < / td >
< td > composable< / td >
< / tr >
< / tbody >
< / table >
< / div >
< / section >
{ /if }
{ #if tab === 'morfo' }
< section data-uix-section >
< h2 data-uix-section-title > Morfo contract< / h2 >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Field< / th > < th > Value< / th > < / tr > < / thead >
< tbody >
< tr >< td class = "name" > name</ td >< td > { passwordFieldMorfo . name } </ td ></ tr >
< tr >< td class = "name" > kebab</ td >< td >< code > { passwordFieldMorfo . kebab } </ code ></ td ></ tr >
< tr >< td class = "name" > scope</ td >< td > { passwordFieldMorfo . scope . join ( ', ' )} </ td ></ tr >
< tr >< td class = "name" > apg</ td >< td >< a href = { passwordFieldMorfo . apg } target="_blank" rel = "noreferrer" > textbox</ a ></ td ></ tr >
< tr >< td class = "name" > parts</ td >< td > { partsList . length } </ td ></ tr >
< tr >< td class = "name" > events</ td >< td > { events . length } </ td ></ tr >
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > Parts< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead >
< tr >
< th > kebab< / th >
< th > marker< / th >
< th > element< / th >
< th > role< / th >
< th > archetype< / th >
< th > optional< / th >
< / tr >
< / thead >
< tbody >
{ #each partsList as part ( part . kebab )}
< tr >
< td class = "name" > { part . kebab } </ td >
< td >< code data-uix-part-marker > [{ part . marker } ]</ code ></ td >
< td class = "type" > < { part . defaultElement } > </ td >
< td class = "type" > { part . role ?? '—' } </ td >
< td class = "type" > { part . archetype ?? '—' } </ td >
< td class = "default" > { part . optional ? 'yes' : 'no' } </ td >
< / tr >
{ /each }
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > Events< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > name< / th > < th > family< / th > < th > verb< / th > < th > target< / th > < th > intent< / th > < / tr > < / thead >
< tbody >
{ #each events as e ( e . name )}
< tr >
< td >< code > { e . name } </ code ></ td >
< td > { e . semantic ? . family ?? '—' } </ td >
< td > { e . semantic ? . verb ?? '—' } </ td >
< td >< code > { e . semantic && 'target' in e . semantic ? e . semantic . target . target : '—' } </ code ></ td >
< td > { e . semantic && 'intent' in e . semantic && typeof e . semantic . intent === 'string' ? e . semantic . intent : '—' } </ td >
< / tr >
{ /each }
< / tbody >
< / table >
< / div >
< / section >
{ /if }
{ #if tab === 'sema' }
< section data-uix-section >
< h2 data-uix-section-title >
< span data-uix-layer-badge = "sema" > sema< / span > · events
< / h2 >
< p data-uix-section-desc >
Type a character to fire < code > commit-input< / code > . Press Enter to commit
< code > commit-submit< / code > . Click the eye to fire
< code > shift-toggle-visibility< / code > . Press Caps Lock while the input is focused to fire
< code > shift-caps-state< / code > . Use ▶ play to dispatch the event without the gesture.
< / p >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > name< / th > < th > family< / th > < th > verb< / th > < th > sequence< / th > < th > intent< / th > < th > < / th > < / tr > < / thead >
< tbody >
{ #each events as action ( action . name )}
{ @const intentDecl = 'intent' in action . semantic ? action.semantic.intent : undefined }
{ @const effectiveIntent =
typeof intentDecl === 'string' ? intentDecl : undefined}
< tr >
< td >< code > { action . name } </ code ></ td >
< td > { action . semantic . family } </ td >
< td > { action . semantic . verb ?? '—' } </ td >
< td > { action . semantic . sequence ?? '—' } </ td >
< td > { effectiveIntent ?? '—' } </ td >
< td >
< button
data-uix-play
onclick={() => {
const part = action.semantic.target.target;
const target = (stageRef?.querySelector(`[data-password-field-${ part } ]`) ??
stageRef?.querySelector('[data-password-field]') ??
stageRef) as HTMLElement | null;
if (!target) return;
void uix.events?.emit({
name: action.name,
family: action.semantic.family,
target,
...(effectiveIntent ? { intent : effectiveIntent } : {} )
});
}}>▶ play< /button
>
< / td >
< / tr >
{ /each }
< / tbody >
< / table >
< / div >
< / section >
{ /if }
{ #if tab === 'recipe' }
< section data-uix-section >
< h2 data-uix-section-title > Eidos recipe< / h2 >
< p data-uix-section-desc >
Recipe lives in < code > src/uix/eidos/components/password-field/password-field.css< / code > .
Provider is the inline-flex container; tokens are private (< code > --_password-field-*< / code > ).
CapsLock indicator and StrengthMeter have their own data-* attribute states.
< / p >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Selector< / th > < th > Owner< / th > < th > Purpose< / th > < / tr > < / thead >
< tbody >
< tr >
< td class = "name" > < code > [data-password-field]< / code > < / td >
< td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td >
< td > Provider marker. Inline-flex container with border + focus ring.< / td >
< / tr >
< tr >
< td class = "name" > < code > [data-password-field][data-size='xs'..'xl']< / code > < / td >
< td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td >
< td > Padding + font-size + radius scale.< / td >
< / tr >
< tr >
< td class = "name" > < code > [data-password-field][data-color='primary'..'loss']< / code > < / td >
< td > < span data-uix-tag data-kind = "eidos" > eidos< / span > < / td >
< td > Maps to focus accent token.< / td >
< / tr >
< tr >
< td class = "name" > < code > [data-password-field][data-focused]< / code > < / td >
< td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td >
< td > Focus ring via box-shadow color-mix.< / td >
< / tr >
< tr >
< td class = "name" > < code > [data-password-field-input]< / code > < / td >
< td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td >
< td > Input cell. Letter-spacing increased for legibility while masked.< / td >
< / tr >
< tr >
< td class = "name" > < code > [data-password-field-visibility-trigger]< / code > < / td >
< td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td >
< td > Eye toggle button. Hover background + focus outline.< / td >
< / tr >
< tr >
< td class = "name" > < code > [data-password-field-caps-lock-indicator][data-active]< / code > < / td >
< td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td >
< td > Risk-tinted pill, hidden when inactive.< / td >
< / tr >
< tr >
< td class = "name" > < code > [data-password-field-strength-meter][data-strength='1'..'4']< / code > < / td >
< td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td >
< td > Active segments tint per strength bucket (loss / risk / fulfill / affirm).< / td >
< / tr >
< / tbody >
< / table >
< / div >
< / section >
{ /if }
{ #if tab === 'a11y' }
< section data-uix-section >
< h2 data-uix-section-title > Accessibility< / h2 >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Concern< / th > < th > Contract< / th > < / tr > < / thead >
< tbody >
< tr >
< td class = "name" > Role< / td >
< td >
Native < code > < input> < / code > with < code > type="password"< / code > (or
< code > "text"< / code > when visible). Implicit < code > role="textbox"< / code > .
< / td >
< / tr >
< tr >
< td class = "name" > Label< / td >
< td >
Defaults to translated 'Password' / 'Contraseña'. When inside a
< code > < Field> < / code > the Field.Label wins via < code > aria-labelledby< / code > .
< / td >
< / tr >
< tr >
< td class = "name" > Autofill< / td >
< td >
< code > autocomplete="current-password"< / code > (login) or
< code > "new-password"< / code > (registration / password change). Drives password
manager behavior.
< / td >
< / tr >
< tr >
< td class = "name" > Visibility toggle< / td >
< td >
< code > < button type="button" aria-pressed={ '{visible}' } aria-label="Show/Hide
password"> < / code > . < code > tabindex="-1"< / code > so it doesn't break the form's
natural tab order — the user toggles via mouse / programmatic focus instead.
< / td >
< / tr >
< tr >
< td class = "name" > Caps Lock detection< / td >
< td >
Soma listens to keydown/keyup on the input and reads
< code > KeyboardEvent.getModifierState('CapsLock')< / code > . Indicator carries
< code > role="status"< / code > + < code > aria-live="polite"< / code > so screen readers
announce when Caps Lock turns on while typing.
< / td >
< / tr >
< tr >
< td class = "name" > Strength meter< / td >
< td >
< code > role="meter"< / code > with < code > aria-valuemin=0< / code > ,
< code > aria-valuemax=4< / code > , < code > aria-valuenow< / code > . Visual segments are
< code > aria-hidden< / code > ; only the label is announced.
< / td >
< / tr >
< tr >
< td class = "name" > Keyboard< / td >
< td >
< code > Enter< / code > fires < code > commit-submit< / code > . No other keys hijacked —
selection / cursor movement / paste all behave natively.
< / td >
< / tr >
< tr >
< td class = "name" > Disabled / readonly< / td >
< td >
Mirrored to native attributes AND ARIA. The visibility trigger also disables when
the input is disabled.
< / td >
< / tr >
< tr >
< td class = "name" > Reduced motion< / td >
< td > All transitions disabled under < code > prefers-reduced-motion: reduce< / code > .< / td >
< / tr >
< / tbody >
< / table >
< / div >
< / section >
{ /if }
< / div >