Layout Components
Welcome back
One Amoleo account for Pets, Connections, and everything else in the family.
Powered by Amoleo·Accounts
No account yet? Signing in creates one the first time — there's nothing separate to fill in.
No local password field anywhere — the button redirects to Accounts' hosted sign-in, which handles both signing in and registration. Coloured with Pets' own --accent (#D9603F); Connections' AuthScreen.jsx is the other live implementation, same shape.
Family auth box — the login/register component
The sign-in-or-create-an-account card every logged-in Amoleo product shows
before a visitor is authenticated. Amoleo-Connections' AuthScreen.jsx /
.auth-card is the reference implementation — this doc writes down what
about it is shared family design versus what any product wearing it is
free to change.
docs/family-header.md already named Connections as this pattern's origin
("Two states, not three... modelled on Connections' actual implementation")
and recorded that Connections' component "becomes the canonical one — not
just for Connections." This doc is that promise kept: the component's own
mechanics, moved out of the header spec (which only needed to say where it
sits) and into a spec of its own, the same split family-wordmark.md made
for the header and footer sizes that reference it.
Where it sits inside a page is the header spec's job, not this one's —
desktop hero + in-page form vs. mobile sticky pop-down, breakpoints, how it
relates to the header bar. See family-header.md, "Two states, not three."
This doc covers the card itself: its structure, its states, and — the part
that actually varies product to product — where it takes its colour from.
node tools/check-auth-box.mjs
0. Two variants, one shell (#62, 30 Sept 2026)
There is no single "reference repo". The card comes in two legitimate
variants that share one shell (.auth-card, .auth-heading,
.auth-tagline, .auth-submit, .auth-switch):
- Redirect — Connections, Pets, and Accounts' own root page (#53). No
fields. A heading, a per-site tagline, one primary call to action (an
<a>, because it navigates cross-origin), a "Powered by Amoleo · Accounts" caption (.auth-accounts-caption, real text, links to Accounts) and a per-site footnote line (.auth-switch) that explains that signing in also creates an account. - Terminal — Accounts' own
/signinscreen only, because it is the one place credentials are actually entered. The same shell holding.auth-tabsand the fields (§2). It has no caption or footnote: the family header above already carries the wordmark, and "powered by Accounts" reads oddly on Accounts.
What varies per site: the accent (--accent / --on-accent), the
tagline, and the footnote. Everything else is shared CSS
(tools/family-css/components/auth-box.css, compiled into base.css).
The redirect variant's JSX is shared too (Amoleo-Accounts#63): the
AuthRedirectCard component in
family-ui/, published as
@valadamoleo/family-ui, takes those three per-site things (plus the
heading, CTA label, link and a children slot) as props. It is a plain
<a href> — the shape Connections' sign-in already has; an app that needs
click-time logic adapts to the link (Pets, for its login nonce, on its own
adoption card). It is delivered like the CSS, through each app's isolated
scripts/family-css fetcher, not as a normal client/ dependency — see
family-css-system.md, "Shared components:
@valadamoleo/family-ui", and family-ui/README.md for the adoption
checklist. The terminal variant's JSX stays in Accounts (its only
consumer). Website, Images and Rissbrook (no React / no login) are
unaffected: a separate package, so family-css stays CSS-only.
Size rules (checked by eye, not by check-auth-box.mjs): heading 24px;
call to action and every other control at least 44px; every line that carries
meaning at least 16px at ≤780px (labels, tagline, tabs, footnote, caption,
hints, problems, errors). The 14px caption is decorative-adjacent text on
desktop but is real, readable text at ≥4.5:1 in both themes.
Accent contrast is signed off in auth-box.json's contrastSignedOff
(text 4.5:1, button edge / focus ring 3:1 against --panel). A new product
accent must be added there when its card ships.
1. One component, five products' worth of accent
The whole point: the box looks identical in shape across every product and
never identical in colour. Tabs, fields, buttons, spacing, radii — all one
shared design. The highlight colour — the active tab, the focus ring, the
submit button, the link hover — is always the current product's own
--accent, never a fixed hex baked into the component.
This is the same relationship the footer lockup has to --brand-* (shared
structure, per-product colour — family-footer.md §2) and the header's
link/button rule has to each site's theme tokens (family-header.md §4).
The auth box is the third surface that shape applies to, and the one where
getting it wrong is easiest to miss: a hardcoded #0b6e8f (Connections'
own accent) copied into Pets' version of this component would render, would
look intentional, and would be wrong the day anyone reused the component
without also swapping the colour by hand.
Two tokens carry the whole rule:
| Token | Used for |
|---|---|
--accent |
Active tab background, input focus outline, submit button, link/hover colour |
--on-accent |
Text/icon colour sitting on top of --accent fills (the active tab's label, the submit button's label) |
Everything else the card uses is neutral — family-theme.md's shared base
(panel, bg, text, muted, border) plus the semantic --danger /
--danger-tint pair for the error state, under whatever names each repo's
own theme gives them (family-theme.md §2 already documents that Website,
Connections and Pets don't agree on names for the same five concepts — the
auth box inherits that aliasing, it doesn't invent a second scheme).
Checked, not just written down: check-auth-box.mjs reads the CSS rule
that styles the active tab and the submit button in every adopted repo and
fails if either sets its highlight colour to a literal hex/rgb() instead of
a var(--...) reference — the same shape of check checkSuffixColour
already runs for the wordmark (family-theme.md §3.5). It cannot confirm
the variable resolves to that product's own accent rather than someone
else's (that needs the alias map a human keeps current in auth-box.json,
same as baseTheme.repos.<name>.aliases) — only that it isn't a number.
2. Structure — the part that's a literal shared component
Unlike the header (rules, not one markup block — different repos need
genuinely different content) the auth box is one component, the way the
footer is: the same tabs, the same fields, the same states, copied into each
repo rather than each repo inventing its own shape. tools/auth-box.json's
classes section is the literal class-name contract check-auth-box.mjs
looks for.
.auth-card— the outer card.min(420px, 100%)wide, padded, sitting on--panelwith the theme's standard border and shadow..auth-heading/.auth-tagline— the card's own title ("Welcome back" / "Create an account") and one line of supporting copy underneath, both swapping with the mode..auth-tabscontaining two.auth-tab, one.active— sign in and create account are tabs sharing one track, not a link buried at the bottom of the form. Which of the two things you're doing is visible before you fill anything in, not discovered afterwards on submit.role="tablist"/role="tab"/aria-selectedthroughout — this is real tab semantics, not styled buttons pretending to be tabs. Since #62 that means the full APG pattern: a rovingtabindex(only the selected tab is in the tab order), Left/Right/Home/End move focus and select, and each tab'saria-controlspoints at onerole="tabpanel"(labelled by the selected tab) holding the form.- Fields, each a
<label>+<input>pair, plus one of:.field-hint— quiet, permanent help text (password length, what a username becomes part of) shown while the field is still valid or untouched..field-problem— the same slot, replaced with a validation message once the field is touched and invalid. One slot, two states, never both at once.
.password-fieldwrapping the password<input>plus a.password-toggle— the show/hide control sits inside the field (absolutely positioned,padding-righton the input to clear it) rather than beside it, so the row never changes height when the control appears..auth-error— one slot for a server-rejected submit (wrong password, duplicate email), left-bordered in--danger,role="alert"..auth-submit— full-width primary button, fixedmin-height: 44pxso a busy-state spinner appearing beside the label never resizes the row..auth-accounts-caption(redirect variant only) — "Powered by Amoleo · Accounts", under the call to action..auth-switch— one line under the card ("No account yet? Create one") that also flips the tab — a second way into the same toggle, not a second interaction.
Reference markup: family-ui/AuthRedirectCard.jsx for the redirect variant
(§0); Accounts' client/src/components/AuthCard.jsx for the terminal one.
The CSS itself was extracted from Connections' /* Sign in and create account */ block into
tools/family-css/components/auth-box.css,
compiled into family-css/base.css — see
docs/family-css-system.md. Connections wired the
shared file in and completed the --panel-2/--border rename
family-css-system.md §4 describes on 6 Aug 2026 — its own duplicated auth
box CSS is gone from styles.css now, replaced by the shared file plus two
small per-context overrides (.landing-auth .auth-card's padding delta,
noted inline). What's canonical lives in the one file now, not two
converging copies.
3. Validation is touched-based, not eager
A field only shows .field-problem once it has been left (onBlur) or the
form has been submitted — never while the visitor is still typing the first
character. Telling someone their email is invalid before they've finished
typing it is nagging, not help. This is a behavioural rule, not something a
text-search checker can verify from CSS — it's listed here so a reimplementation
doesn't quietly drop it, and is one of the items in §6 a human still confirms
by eye.
What each product validates is its own business rule and stays that way. Password length (Connections: 10 characters), username shape and reserved words, whatever server-side rules a product enforces — none of that is shared design, and this spec does not try to make it so. What's shared is the pattern — touched-based reveal, a hint that becomes a problem, one slot for both — not the specific rules run through it.
4. States — two tabs, not three, and not a modal
Corrected the same day family-header.md was: the login/register form is
not a separate screen from the marketing/landing content, and sign-in and
register are not two separate screens either. There is exactly one pre-auth
state, and inside it exactly one card whose two tabs swap which half of the
form is visible. No route change, no modal, no page navigation between
"login" and "register" — switchMode() just flips local state.
5. Per-repo status
27 Aug 2026 — the reference implementation moved, and this table hadn't
caught up until now (corrected 25 Sept 2026). Connections and Pets both cut
over their main sign-in surface to Amoleo Accounts OIDC sign-in that day
(Connections' issue #4; Pets' #330/#331/#332) — per
Amoleo-Accounts/docs/Architecture.md's "no dual-auth period," each app's own
tabs/fields form is gone, replaced by one "Sign in with Amoleo Accounts"
button that redirects to Accounts' own hosted screen. Amoleo-Accounts is
now the only repo where this component's actual tabs/fields shape is live
— its AuthCard.jsx, already recorded below as adopted via #6, is what a
fresh reimplementation should read as the live reference, not Connections'.
Connections' AuthScreen.jsx remains the historical origin (§1–4 above was
extracted from it, and nothing about the component's design changed — only
which repos still render it), but treat that as history, not a current
pointer.
| Repo | Applicable? | Adopted? | Notes |
|---|---|---|---|
| Amoleo-Connections | Yes | No — superseded 27 Aug 2026 (this is the redirect variant, §0 — a legitimate shape, adopting the shared redirect card CSS is on Connections' own card) | AuthScreen.jsx / Landing.jsx render one redirect button now, no tabs/fields. Was the reference implementation until this date — see auth-box.json's entry for the detail. |
| Amoleo-Pets | Yes | No — superseded 27 Aug 2026 | AuthCard.jsx likewise reduced to one redirect button (per Pets' own #332 — not independently verified against Pets' checked-out code by whoever last edited this entry). LoginForm.jsx/RegisterForm.jsx reportedly still live behind ClaimPetView.jsx's separate claim flow — an open question for Pets' own owner, not resolved here. |
| Amoleo-Website | No | — | No login system. Nothing renders in its place. |
| Amoleo-Rissbrook | No | — | No login system, and none planned — a static genealogy archive. |
| Amoleo-Accounts | Yes | Yes — the terminal variant (§0) | client/src/components/AuthCard.jsx, wired to the server-side interaction plumbing (server/src/routes/interactionApi.js) and placed per Decision 9 (family-header.md). The only repo where a visitor still fills in tabs/fields to sign in or register — every other app now just redirects here. See auth-box.json's own entry for its two documented departures from the original spec (autofocus scoping, extra ARIA wiring). |
| Amoleo-Todo | Unknown | No — no code yet | Design-only; whether Todo gets its own login or rides Accounts' SSO isn't decided. Not registered in auth-box.json until it is. |
6. What to check afterwards
Automated (check-auth-box.mjs):
- Every adopted repo's CSS defines the full class set in §2 —
.auth-card,.auth-tabs/.auth-tab/.active,.password-field/.password-toggle,.field-hint/.field-problem,.auth-error,.auth-submit,.auth-switch. - The active-tab rule and the submit-button rule set their highlight colour
through a
var(--...)reference, never a literal hex orrgb().
By eye, on top of that:
- The two tabs really are keyboard- and screen-reader-operable as tabs
(
role="tablist"/role="tab"/aria-selected), not just styled to look like them. - A field's validation message appears only after it's been left or the form submitted — not while the visitor is still typing.
- The password toggle sits inside the field without shifting its height, in both states.
- There is one pre-auth state, not a separate route for register.
- The
var(--accent)the active tab and submit button resolve to is genuinely that product's own accent — the family's shared base theme (family-theme.md) plus each product's own colour (tools/tokens/base.tokens.json), not another product's or a leftover generic value. This is the same class of bug §3.5 offamily-theme.mdfound in Connections' own wordmark — a fallback that resolves to something, just not the right something.
Generated from PetWeightTracker/client/src/components/AuthCard.jsx + K-Drama Connections/client/src/components/AuthScreen.jsx (live implementations — docs/family-auth-box.md and tools/auth-box.json are stale, see note above). Edit there and re-run npm run build in docs-site-src/ — never hand-edit a page.