Skip to content
BigBang

Popular

    ↑↓Navigate↵OpenEscClose
    TR

    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.

    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.

    Live example, StorybookOpen in Storybook
    Live example, StorybookOpen in Storybook
    Terminal window
    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>
    );
    }
    1. Brand and an h1 “Sign in” inside main (AuthShell).
    2. Username field (TextInput, autoComplete="username").
    3. Password field with a show/hide IconButton at its end (aria-pressed).
    4. “Remember this device” Checkbox and a “Forgot password” Link.
    5. Generic error: a danger InlineAlert (announce) that appears after a rejected attempt.
    6. Full-width primary Button with a loading state while waiting.
    • 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 announce takes role="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).

    TextInput, IconButton, Checkbox, Link, Button, InlineAlert. Patterns: Forms and validation, Loading, Session timeout and one-time code.