Sign Up Block
SignUp is a controlled registration form block built on MUI.
Installationโ
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-signup-block
yarn add @nodeblocks/frontend-signup-block
pnpm add @nodeblocks/frontend-signup-block
bun add @nodeblocks/frontend-signup-block
What You Needโ
| Item | Why it matters |
|---|---|
data | Single source of truth for form state |
onDataChange | Receives updated state + metadata for validation/analytics |
errors (optional) | Displays field-level error feedback |
termsOfUseUrl (optional) | Target URL for the terms checkbox link (/terms-of-use by default) |
privacyPolicyUrl (optional) | Target URL for the privacy checkbox link (/privacy-policy by default) |
loginUrl (optional) | Target URL for the sign-in link (/auth/login by default) |
SignUp does not own form state. Keep state in your app and pass it through data. Default shape includes email, password, agreesUserAgreement, and agreesPrivacyPolicy.
Code Examplesโ
- Quick Start
- Labels and URLs
- Form Errors
- Compound Components
- Block Override
function Example() { const defaultData = { email: '', password: '', agreesUserAgreement: false, agreesPrivacyPolicy: false, }; const [data, setData] = React.useState(defaultData); return ( <SignUp data={data} onDataChange={(nextData) => { setData(nextData); }} /> ); }
function Example() { const defaultData = { email: '', password: '', agreesUserAgreement: false, agreesPrivacyPolicy: false, }; const [data, setData] = React.useState(defaultData); const labels = { signUpTitle: 'Create account', emailField: 'Work email', passwordField: 'Password', termsOfUse: 'I agree to the Terms of Use', privacyPolicy: 'I agree to the Privacy Policy', signUpButton: 'Register', gotoSignIn: 'Sign in', backButton: 'Back to previous page', }; const placeholders = { emailField: 'name@company.com', passwordField: 'At least 8 characters', }; return ( <SignUp data={data} onDataChange={(nextData) => { setData(nextData); }} labels={labels} placeholders={placeholders} termsOfUseUrl="/terms-of-use" privacyPolicyUrl="/privacy-policy" loginUrl="/auth/login" /> ); }
Pass labels and placeholders on the root for copy overrides, or set them on individual subcomponents (see Compound Components tab). Checkbox labels (termsOfUse, privacyPolicy) render as links using termsOfUseUrl and privacyPolicyUrl. Use loginUrl for the default sign-in link, or override via SignUp.GotoSignIn children.
function Example() { const defaultData = { email: '', password: '', agreesUserAgreement: false, agreesPrivacyPolicy: false, }; const [data, setData] = React.useState(defaultData); const [errors, setErrors] = React.useState({}); const handleDataChange = (nextData, meta) => { const { [meta.name]: _removed, ...restErrors } = errors; let nextErrors = restErrors; // Validate required fields on blur (same pattern as storybook) if (meta.cause === 'blur') { nextErrors = { ...restErrors }; if (meta.name === 'email' && !nextData.email) nextErrors.email = 'Email is required.'; if (meta.name === 'password' && !nextData.password) nextErrors.password = 'Password is required.'; } setErrors(nextErrors); setData(nextData); }; return ( <SignUp data={data} errors={Object.keys(errors).length ? errors : undefined} onDataChange={handleDataChange} /> ); }
Clear the active field error first, then re-validate required fields on blur.
Use child blocks to customize layout while keeping controlled state behavior.
function Example() { const defaultData = { email: '', password: '', agreesUserAgreement: false, agreesPrivacyPolicy: false, }; const [data, setData] = React.useState(defaultData); const [errors, setErrors] = React.useState({}); const handleDataChange = (nextData, meta) => { const {[meta.name]: _removed, ...restErrors} = errors; let nextErrors = restErrors; // Validate required fields on blur (same pattern as storybook) if (meta.cause === 'blur') { nextErrors = {...restErrors}; if (meta.name === 'email' && !nextData.email) nextErrors.email = 'Email is required.'; if (meta.name === 'password' && !nextData.password) nextErrors.password = 'Password is required.'; } setErrors(nextErrors); setData(nextData); }; return ( <SignUp data={data} errors={Object.keys(errors).length ? errors : undefined} onDataChange={handleDataChange} termsOfUseUrl="/terms-of-use" privacyPolicyUrl="/privacy-policy" loginUrl="/auth/login" > <SignUp.SignUpTitle>Create Your Account</SignUp.SignUpTitle> <SignUp.EmailField label="Email Address" placeholder="Enter your email" /> <SignUp.PasswordField label="Password" placeholder="Enter your password" /> <SignUp.TermsOfUse termsOfUseUrl="/terms-of-use" /> <SignUp.PrivacyPolicy privacyPolicyUrl="/privacy-policy" /> <SignUp.SignUpButton>Sign Up</SignUp.SignUpButton> <SignUp.GotoSignIn loginUrl="/auth/login"> <span> Already have an account? <a href="#auth/login">Sign In</a> </span> </SignUp.GotoSignIn> </SignUp> ); }
Use function children to override default blocks and order.
function Example() { const defaultData = { email: '', password: '', agreesUserAgreement: false, agreesPrivacyPolicy: false, }; const [data, setData] = React.useState(defaultData); const [errors, setErrors] = React.useState({}); const handleDataChange = (nextData, meta) => { const {[meta.name]: _removed, ...restErrors} = errors; let nextErrors = restErrors; // Validate required fields on blur (same pattern as storybook) if (meta.cause === 'blur') { nextErrors = {...restErrors}; if (meta.name === 'email' && !nextData.email) nextErrors.email = 'Email is required.'; if (meta.name === 'password' && !nextData.password) nextErrors.password = 'Password is required.'; } setErrors(nextErrors); setData(nextData); }; return ( <SignUp data={data} errors={Object.keys(errors).length ? errors : undefined} onDataChange={handleDataChange}> {({defaultBlocks, defaultBlockOrder}) => ({ blocks: { ...defaultBlocks, customNotification: ( <div style={{ background: '#eef4ff', border: '1px solid #cddcff', borderRadius: '8px', padding: '12px', fontSize: '14px', }} > Welcome! Fill in the form to create your account. </div> ), }, blockOrder: ['customNotification', ...defaultBlockOrder], })} </SignUp> ); }
When to use block overrides
Use overrides when you need to change order, replace default UI blocks, or inject custom content while preserving shared state handling. Default order is signUpTitle, emailField, passwordField, termsOfUse, privacyPolicy, signUpButton, gotoSignIn, backButton. Remove backButton from blockOrder if you do not need it. Use blockSpacingBefore to add responsive spacing before specific blocks (block override pattern only).
Important Propsโ
Core Propsโ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
data | SignUpFormData ({ email, password, agreesUserAgreement, agreesPrivacyPolicy } or extended Record<string, unknown>) | Yes | - | Current form data object |
onDataChange | (data, meta) => void | Yes | - | Called on updates; meta includes name, value, cause (input, change, blur, clear, reset, programmatic), optional event |
errors | { [fieldName: string]: string | string[] } | No | undefined | Field errors keyed by data field name (e.g. email, password, agreesUserAgreement, agreesPrivacyPolicy) |
Content Propsโ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
labels | { signUpTitle?, emailField?, passwordField?, termsOfUse?, privacyPolicy?, signUpButton?, gotoSignIn?, backButton? } | No | signUpTitle: 'Create Your Account', emailField: 'Email Address', passwordField: 'Password', termsOfUse: 'Accept the terms of use.', privacyPolicy: 'Accept the privacy policy.', signUpButton: 'Sign Up', gotoSignIn: 'Already have an account? Sign In.', backButton: 'Back to previous page' | Override default text labels |
placeholders | { emailField?, passwordField? } | No | emailField: 'Enter your email address', passwordField: 'Enter your password' | Override email/password placeholders |
termsOfUseUrl | string | No | /terms-of-use | Target URL for terms link |
privacyPolicyUrl | string | No | /privacy-policy | Target URL for privacy link |
loginUrl | string | No | /auth/login | Target URL for "Go to sign in" link |
Layout and Composition Propsโ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | BlocksOverride | ReactNode | No | undefined | Override default blocks or provide custom JSX children |
blockSpacingBefore | Partial<Record<string, number | Partial<Record<'xs' | 'sm' | 'md' | 'lg' | 'xl', number>>>> | No | { passwordField: 3, termsOfUse: 3, privacyPolicy: 0 } | Theme spacing before listed blocks; applies to default blocks or block override pattern only |
component | React.ElementType | No | 'form' | Root element type |
className | string | No | undefined | Class name for the root container |
sx | SxProps | No | undefined | MUI system styles for the root container |
SignUp also inherits StackProps<'form'> (except children), so standard form/stack props like onSubmit, id, and noValidate are supported. Default block order without overrides: signUpTitle, emailField, passwordField, termsOfUse, privacyPolicy, signUpButton, gotoSignIn, backButton (defaultBlockOrder).
Default UI Blocksโ
| Block | Built on | Notes |
|---|---|---|
SignUp (root) | Stack (component="form") | Controlled form container; max width 496px, centered card layout |
SignUp.SignUpTitle | Typography | Title text block (variant="h4"); override via labels.signUpTitle or children |
SignUp.EmailField | TextField | Email input (name="email", autoComplete="username"); default label Email Address, placeholder Enter your email address |
SignUp.PasswordField | TextField + IconButton | Password input with visibility toggle; autoComplete="new-password"; default label Password, placeholder Enter your password |
SignUp.TermsOfUse | Checkbox + FormControlLabel + Link | Checkbox (name="agreesUserAgreement"); label is a link to termsOfUseUrl (target="_blank") |
SignUp.PrivacyPolicy | Checkbox + FormControlLabel + Link | Checkbox (name="agreesPrivacyPolicy"); label is a link to privacyPolicyUrl (target="_blank") |
SignUp.SignUpButton | Button | Submit button (variant="contained", size="large", type="submit", fullWidth) |
SignUp.GotoSignIn | Box + Link | Sign-in navigation; uses loginUrl or custom children |
SignUp.BackButton | Button | Secondary action (variant="outlined", size="large", type="button", fullWidth); included in default orderโomit from blockOrder if unused |
Extra field primitivesโ
| Primitive | Built on | Notes |
|---|---|---|
SignUp.TextField | TextField | Generic text input |
SignUp.TimeField | TextField | Time input (type="time") |
SignUp.CheckboxField | Checkbox + FormControl | Checkbox with helper/error text |
SignUp.SelectField | Select + TextField | Select input rendered through MUI TextField |
TypeScriptโ
import {SignUp, SignUpFormData} from '@nodeblocks/frontend-signup-block';
type MySignUpData = SignUpFormData & {
companyName?: string;
};