Templates
Sign-in
Username and password sign-in with inline errors, one generic credential error and a Caps Lock hint.
The sign-in block builds a two-field form inside AuthShell. Copy the code and connect your own authentication call through onSignIn.
Preview
Section titled “Preview”Submit the empty form: focus moves to the first empty field. Fill in both fields and submit; the sample call rejects them and the generic error appears. The brand and mode switches in the page header restyle the preview too.
pnpm add @bigbang/ui| Data | Type | Note |
|---|---|---|
onSignIn |
(values) => Promise<boolean> |
Returning false shows the generic credential error. |
initialError |
boolean |
Shows a rejection passed in by a server redirect. |
lang |
'tr' | 'en' |
Copy and library strings. |
import { Button, Checkbox, IconButton, Inline, InlineAlert, Link, Stack, TextInput } from '@bigbang/ui';import { useRef, useState, type FormEvent, type KeyboardEvent } from 'react';import type { Lang } from '../../patterns/lang';import { AuthShell } from '../../templates/auth-shell/AuthShell';
const copy = { tr: { title: 'Giriş yapın', description: 'Devam etmek için hesabınıza giriş yapın.', username: 'Kullanıcı adı', password: 'Parola', show: 'Parolayı göster', remember: 'Bu cihazı hatırla', submit: 'Giriş yap', signingIn: 'Giriş yapılıyor', forgot: 'Parolamı unuttum', usernameEmpty: 'Kullanıcı adınızı girin.', passwordEmpty: 'Parolanızı girin.', credentials: 'Kullanıcı adı veya parola hatalı.', capsLock: 'Büyük harf kilidi açık.', }, en: { title: 'Sign in', description: 'Sign in to your account to continue.', username: 'Username', password: 'Password', show: 'Show password', remember: 'Remember this device', submit: 'Sign in', signingIn: 'Signing in', forgot: 'Forgot password', usernameEmpty: 'Enter your username.', passwordEmpty: 'Enter your password.', credentials: 'The username or password is wrong.', capsLock: 'Caps Lock is on.', },};
type Errors = { username?: string; password?: string };
export type LoginBlockProps = { lang?: Lang; /** Shows the credential error, as after a rejected attempt. */ initialError?: boolean; /** Called with the entered values once both are filled in. Resolve `false` to show the credential error. */ onSignIn?: (values: { username: string; password: string; remember: boolean }) => Promise<boolean>;};
const demoSignIn = () => new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 1200));
/** Sign-in screen: two fields, inline errors, one generic credential error and a Caps Lock hint. */export function LoginBlock({ lang = 'tr', initialError = false, onSignIn = demoSignIn }: LoginBlockProps) { const t = copy[lang]; const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [remember, setRemember] = useState(false); const [visible, setVisible] = useState(false); const [capsLock, setCapsLock] = useState(false); const [errors, setErrors] = useState<Errors>({}); const [rejected, setRejected] = useState(initialError); const [pending, setPending] = useState(false); const formRef = useRef<HTMLFormElement>(null);
const onSubmit = async (event: FormEvent) => { event.preventDefault(); const found: Errors = {}; if (!username.trim()) found.username = t.usernameEmpty; if (!password) found.password = t.passwordEmpty; setErrors(found); setRejected(false); const first = (['username', 'password'] as const).find((key) => found[key]); if (first) { formRef.current?.querySelector<HTMLInputElement>(`input[name="${first}"]`)?.focus(); return; } setPending(true); const accepted = await onSignIn({ username, password, remember }); setPending(false); // The message never says which value was wrong. if (!accepted) setRejected(true); };
const onKey = (event: KeyboardEvent<HTMLInputElement>) => setCapsLock(event.getModifierState('CapsLock'));
return ( <AuthShell lang={lang} title={t.title} description={t.description}> <form ref={formRef} noValidate onSubmit={onSubmit} aria-labelledby={`auth-title-${lang}`}> <Stack gap="lg"> <TextInput label={t.username} name="username" autoComplete="username" required value={username} onValueChange={(value) => setUsername(value)} {...(errors.username ? { error: errors.username } : {})} /> <TextInput label={t.password} name="password" type={visible ? 'text' : 'password'} autoComplete="current-password" required value={password} onValueChange={(value) => setPassword(value)} onKeyUp={onKey} onKeyDown={onKey} {...(errors.password ? { error: errors.password } : capsLock ? { description: t.capsLock } : {})} endContent={<IconButton icon={visible ? 'eye-off' : 'eye'} label={t.show} variant="tertiary" size="sm" aria-pressed={visible} onClick={() => setVisible((value) => !value)} />} /> <Inline justify="between" align="center" wrap> <Checkbox label={t.remember} name="remember" checked={remember} onCheckedChange={setRemember} /> <Link href="#" size="sm"> {t.forgot} </Link> </Inline> {rejected ? ( <InlineAlert tone="danger" announce> {t.credentials} </InlineAlert> ) : null} <Button type="submit" fullWidth loading={pending} loadingLabel={t.signingIn}> {t.submit} </Button> </Stack> </form> </AuthShell> );}Structure
Section titled “Structure”- Brand and an
h1“Sign in” insidemain(AuthShell). - Username field (TextInput,
autoComplete="username"). - Password field with a show/hide IconButton at its end (
aria-pressed). - “Remember this device” Checkbox and a “Forgot password” Link.
- Generic error: a
dangerInlineAlert (announce) that appears after a rejected attempt. - Full-width primary Button with a loading state while waiting.
Accessibility
Section titled “Accessibility”- On an empty submit, focus moves to the first invalid field; two fields need no summary (Forms and validation).
- The credential error never says which value was wrong; InlineAlert with
announcetakesrole="alert"and is read once; the tone is also spoken as an “Error:” prefix. - With Caps Lock on, the password field’s description becomes “Caps Lock is on.” and is read with the field.
- The show/hide button keeps its name and reports its state with
aria-pressed. - The password field has
autoComplete="current-password", so password managers work (WCAG 3.3.8).
Components used
Section titled “Components used”TextInput, IconButton, Checkbox, Link, Button, InlineAlert. Patterns: Forms and validation, Loading, Session timeout and one-time code.