@emubrain-studio/auth-ui 0.1.0 contract v1 Edges & themes Design only

The states that are usually skipped

Keyboard and focus, password-manager autofill, a product name long enough to break the header, reduced motion, and all three theme values. None of these is a product decision — they are package-owned, so they are specified once here and every product inherits them.

1Keyboard and focus

One ring, one offset, both themes, every product

Focus order is the DOM order and is fixed by the package: email, password, the inline "Forgot password?" link, then the submit button. A product cannot reorder fields, so it cannot reorder focus. The ring is 2px solid --focus at 2px offset — not the accent, because the accent is product-variable and a focus indicator that changes contrast per product is not an indicator.

2Password managers and autofill

Two rules that make autofill work, both package-owned. The email and password fields carry stable autocomplete semantics (username, current-password, new-password) so managers fill the right box; and the field height and padding leave room for a manager's own overlay glyph on the right, which is why the input is 42px rather than the 36px that would otherwise look tighter. An autofilled field is shown filled and valid — never as a validation error, which is the classic failure when a form validates on mount.

3A product name that does not fit

The contract permits up to 120 characters in branding.logoAlt (z.string().trim().min(1).max(120)), which is far longer than the header can show. So the name truncates with an ellipsis on one line and the mark never moves — it must not wrap to two lines, shrink the mark, or push the card wider. The full string stays available to assistive technology as the logo's accessible name.

4Reduced motion

There is one animation in the entire package — the submit button's in-flight spinner. Under prefers-reduced-motion: reduce it stops rotating and holds as a dimmed ring, so the button still reads as busy without motion. Nothing else animates, so nothing else needs a reduced variant.

5Themes — light, dark, system

The manifest picks one of three; it does not pick the values

A product chooses theme, not the palette. theme is z.enum(["light","dark","system"]), and the dark values are package-owned — otherwise dark mode becomes forty separately-tuned products and the contrast floor cannot be guaranteed. system follows prefers-color-scheme live; the third card below is rendered in whichever mode this browser is currently in.

6The same view, three products

Only logo, accent, accent-foreground and font token differ

This is the whole variation budget, rendered. Three products, three accents, three font tokens, three names — identical structure, identical field order, identical copy, identical error placement. If a fourth product wanted a different button position or a softer password rule, the schema would reject the manifest before it reached a screen.

Presentation only. Aligned to emubrain-auth PR #26 at head c5e5c75. No auth behaviour, cookie, session, origin or secret is defined here.