Skip to content

Sign-up form

'use client';
import { useState, type FormEvent } from 'react';
import { Button, Card, Checkbox, Field, Stack, Text, TextInput } from '@helixui/core';
export interface SignUpFormProps {
onSubmit: (creds: { email: string; password: string }) => Promise<void>;
}
export function SignUpForm({ onSubmit }: SignUpFormProps) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirm, setConfirm] = useState('');
const [agree, setAgree] = useState(false);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const passwordsMatch = password.length >= 8 && password === confirm;
const canSubmit = email && passwordsMatch && agree && !busy;
const submit = async (e: FormEvent) => {
e.preventDefault();
setError(null);
if (!passwordsMatch) { setError('Passwords don\'t match.'); return; }
setBusy(true);
try {
await onSubmit({ email, password });
} catch (err) {
setError(err instanceof Error ? err.message : 'Sign-up failed.');
} finally {
setBusy(false);
}
};
return (
<Card variant="elevated" style={{ padding: 'var(--helixui-space-8)', maxWidth: 400 }}>
<form onSubmit={submit}>
<Stack gap={4}>
<Text size="2xl" weight="semibold">Create an account</Text>
<Field label="Email">
<TextInput type="email" autoComplete="email" value={email} onChange={setEmail} required />
</Field>
<Field label="Password" hint="At least 8 characters.">
<TextInput type="password" autoComplete="new-password" value={password} onChange={setPassword} required minLength={8} />
</Field>
<Field label="Confirm password">
<TextInput type="password" autoComplete="new-password" value={confirm} onChange={setConfirm} required />
</Field>
<Checkbox isSelected={agree} onChange={setAgree}>
I agree to the <a href="/terms">terms</a> and <a href="/privacy">privacy policy</a>.
</Checkbox>
{error ? <Text size="sm" tone="danger">{error}</Text> : null}
<Button type="submit" loading={busy} disabled={!canSubmit}>Create account</Button>
</Stack>
</form>
</Card>
);
}

The “disabled until everything’s valid” UX matches Apple’s HIG and keeps you out of the “user pressed submit and nothing happened” failure mode. The canSubmit check is reactive — flip any field and the button enables.