Sign In Block
SignIn is a controlled sign-in form block built on MUI.
Installationโ
- npm
- yarn
- pnpm
- bun
npm install @nodeblocks/frontend-signin-block
yarn add @nodeblocks/frontend-signin-block
pnpm add @nodeblocks/frontend-signin-block
bun add @nodeblocks/frontend-signin-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 |
signupUrl (optional) | Target URL for the sign-up link (/auth/signup by default) |
resetPasswordUrl (optional) | Target URL for the forgot-password link (/auth/resetpassword by default) |
SignIn does not own form state. Keep state in your app and pass it through data.
Code Examplesโ
- Quick Start
- Labels and URLs
- Form Errors
- Compound Components
- Block Override
function Example() { const defaultData = { email: '', password: '', }; const [data, setData] = React.useState(defaultData); return ( <SignIn data={data} onDataChange={(nextData) => { setData(nextData); }} /> ); }
function Example() { const defaultData = { email: '', password: '', }; const [data, setData] = React.useState(defaultData); const labels = { signInTitle: 'Welcome back', emailField: 'Work email', passwordField: 'Password', signInButton: 'Sign in', gotoSignUp: 'Create an account', resetPassword: 'Forgot your password?', }; const placeholders = { emailField: 'name@company.com', passwordField: 'Enter your password', }; return ( <SignIn data={data} onDataChange={(nextData) => { setData(nextData); }} labels={labels} placeholders={placeholders} signupUrl="/auth/signup" resetPasswordUrl="/auth/resetpassword" /> ); }
Pass labels and placeholders on the root for copy overrides, or set them on individual subcomponents (see Compound Components tab). Use signupUrl and resetPasswordUrl for navigation targets; override link content via SignIn.GotoSignUp or SignIn.ResetPassword children.
function Example() { const defaultData = { email: '', password: '', }; 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 ( <SignIn 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: '', }; 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 ( <SignIn data={data} errors={Object.keys(errors).length ? errors : undefined} onDataChange={handleDataChange} signupUrl="/auth/signup" resetPasswordUrl="/auth/resetpassword" > <SignIn.SignInTitle>Welcome Back</SignIn.SignInTitle> <SignIn.EmailField label="Email Address" placeholder="Enter your email" /> <SignIn.PasswordField label="Password" placeholder="Enter your password" /> <SignIn.SignInButton>Sign In</SignIn.SignInButton> <SignIn.GotoSignUp> <a href="#auth/signup">Create an account</a> </SignIn.GotoSignUp> <SignIn.ResetPassword> <a href="#auth/resetpassword">Forgot password?</a> </SignIn.ResetPassword> </SignIn> ); }
Use function children to override default blocks and order.
function Example() { const defaultData = { email: '', password: '', }; 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 ( <SignIn 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 back! Please sign in to continue. </div> ), }, blockOrder: ['customNotification', ...defaultBlockOrder], })} </SignIn> ); }
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 signInTitle, emailField, passwordField, signInButton, resetPassword, gotoSignUp. Use blockSpacingBefore to add responsive spacing before specific blocks (block override pattern only).
Important Propsโ
Core Propsโ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
data | SignInFormData ({ email, password } 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 path (e.g. email, password, or nested paths when data is extended) |
Content Propsโ
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
labels | { signInTitle?, emailField?, passwordField?, signInButton?, gotoSignUp?, resetPassword? } | No | signInTitle: 'Sign In', emailField: 'Email', passwordField: 'Password', signInButton: 'Sign In', gotoSignUp: 'Create an account', resetPassword: 'Forgot your password?' | Override default text labels |
placeholders | { emailField?, passwordField? } | No | emailField: 'Please enter your email', passwordField: 'Please enter your password' | Override email/password placeholders |
signupUrl | string | No | /auth/signup | Target URL for sign-up link |
resetPasswordUrl | string | No | /auth/resetpassword | Target URL for reset password 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: { xs: 3, sm: 4 } } | 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 |
SignIn also inherits StackProps<'form'> (except children), so standard form/stack props like onSubmit, id, and noValidate are supported. Default block order without overrides: signInTitle, emailField, passwordField, signInButton, resetPassword, gotoSignUp (defaultBlockOrder).
Default UI Blocksโ
| Block | Built on | Notes |
|---|---|---|
SignIn (root) | Stack (component="form") | Controlled form container; max width 496px, centered card layout |
SignIn.SignInTitle | Typography | Title text block (variant="h4"); override via labels.signInTitle or children |
SignIn.EmailField | TextField | Email input (name="email", autoComplete="username") |
SignIn.PasswordField | TextField + IconButton | Password input with visibility toggle; autoComplete="current-password" |
SignIn.SignInButton | Button | Submit button (variant="contained", size="large", type="submit", fullWidth) |
SignIn.ResetPassword | Box + Link | Forgot password link; uses resetPasswordUrl or custom children |
SignIn.GotoSignUp | Box + Link | Sign-up navigation link; uses signupUrl or custom children |
Extra field primitivesโ
| Primitive | Built on | Notes |
|---|---|---|
SignIn.TextField | TextField | Generic text input |
SignIn.TimeField | TextField | Time input (type="time") |
SignIn.CheckboxField | Checkbox + FormControl | Checkbox with helper/error text |
SignIn.SelectField | Select + TextField | Select input rendered through MUI TextField |
TypeScriptโ
import {SignIn, SignInFormData} from '@nodeblocks/frontend-signin-block';
type MySignInData = SignInFormData & {
rememberMe?: boolean;
};