{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "login-form", "title": "Login Form", "description": "A login form worked example (Card + Field + Input + Button) wired with NSWDS components. Copy-and-adapt block — not a published component.", "registryDependencies": [ "https://ui.digital.nsw.gov.au/registry/r/theme.json", "https://ui.digital.nsw.gov.au/registry/r/button.json", "https://ui.digital.nsw.gov.au/registry/r/card.json", "https://ui.digital.nsw.gov.au/registry/r/field.json", "https://ui.digital.nsw.gov.au/registry/r/input.json", "https://ui.digital.nsw.gov.au/registry/r/labeled-separator.json", "https://ui.digital.nsw.gov.au/registry/r/link.json", "https://ui.digital.nsw.gov.au/registry/r/separator.json", "https://ui.digital.nsw.gov.au/registry/r/icons.json" ], "files": [ { "path": "src/patterns/login-form.tsx", "content": "'use client'\n\nimport type * as React from 'react'\n\nimport { Button } from '@/components/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/card'\nimport { Field, FieldDescription, FieldGroup, FieldLabel } from '@/components/field'\nimport { Input } from '@/components/input'\nimport { LabeledSeparator } from '@/components/labeled-separator'\nimport { Link } from '@/components/link'\nimport { Separator } from '@/components/separator'\nimport { IconKey } from '@/icons/key'\nimport { IconLogin } from '@/icons/login'\nimport { IconMail } from '@/icons/mail'\nimport { cn } from '@/lib/utils'\n\nexport function LoginForm({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n
\n \n \n Login to your account\n Choose how you'd like to sign in\n \n \n
\n \n {/*\n * Best-practice sign-in order, most-recommended first:\n * 1. Single sign-on (SSO) — the preferred path\n * 2. Email + password — the traditional fallback\n * 3. Magic link — passwordless, reuses the email\n * Each method is divided by a LabeledSeparator so it reads as a\n * distinct alternative. Only SSO carries primary (solid)\n * emphasis — one primary action per view — with password Login\n * and the magic link stepped down to outline treatments.\n */}\n {/* 1 — Single sign-on: the recommended first option */}\n \n \n \n or sign in with email\n {/* 2 — Email + password.\n * No htmlFor/id wiring: each control sits in a Field, which\n * associates the label (and any description/error) with the input\n * automatically via Base UI, generating ids per instance — so two\n * LoginForms on one page don't collide.\n *\n * autoComplete is not decoration: it is the technique that\n * satisfies WCAG 2.1 SC 1.3.5 Identify Input Purpose (AA), and\n * it is what lets a password manager offer the saved credential.\n * `username` — not `email` — is the correct token for a sign-in\n * identifier even when the control is type=\"email\": managers pair\n * `username` with `current-password` to recognise a login form.\n * (SignUpForm uses `new-password` for the same reason in reverse.)\n * Note that axe cannot catch this — `autocomplete-valid` only\n * checks values that are present, so a missing attribute passes\n * every automated gate. */}\n \n Email\n \n \n \n Password\n \n {/*\n * Placed AFTER the input in DOM (rather than next to the\n * label) so keyboard tab order is email → password → forgot,\n * not email → forgot → password. Visually positioned at the\n * top-right via absolute positioning to preserve the\n * \"Forgot your password?\" alongside-label layout. See WCAG\n * 2.4.3 Focus Order test in\n * login-form.accessibility.stories.tsx.\n */}\n \n Forgot your password?\n \n \n \n \n \n {/* 3 — Magic link: passwordless, sent to the email entered above */}\n or\n \n \n \n We'll send a password-free sign-in link to the email above.\n \n \n \n \n \n Don't have an account? Sign up\n \n \n \n
\n
\n
\n
\n )\n}\n", "type": "registry:component", "target": "components/login-form.tsx" } ], "type": "registry:block", "meta": { "nswdsVersion": "5.1.0" } }