Form Molecules
Available via @stackmates/ui-forms
FormCheckbox
Checkbox with label and description, integrated with TanStack Form. Manages boolean field state.
You must agree before submitting.
FormCheckboxProps
| Prop | Type | Default | Description |
|---|---|---|---|
| form* | FormApi | — | TanStack Form instance |
| name* | keyof TFormData | — | Field name in form data |
| label* | string | — | Checkbox label text |
| helpText | string | — | Help text below checkbox |
Installation
import { FormCheckbox } from '@stackmates/ui-forms';Usage
With description
<FormCheckbox<FormData>
form={form}
name="agreeToTerms"
label="I agree to the terms"
helpText="Required to continue."
/>Accessibility
- Label linked to checkbox via htmlFor
- Description uses aria-describedby
- Keyboard togglable with Space key
FormMoneyInput
Currency input with formatting, decimal handling, and TanStack Form integration.
FormMoneyInputProps
| Prop | Type | Default | Description |
|---|---|---|---|
| form* | FormApi | — | TanStack Form instance |
| name* | keyof TFormData | — | Field name in form data |
| label* | string | — | Field label |
| currency | string | 'USD' | Currency code |
| placeholder | string | — | Placeholder text |
Installation
import { FormMoneyInput } from '@stackmates/ui-forms';Usage
Basic
<FormMoneyInput<FormData>
form={form}
name="amount"
label="Deal Value"
placeholder="0.00"
/>Accessibility
- Input type="text" with inputMode="decimal" for mobile keyboards
- Currency symbol displayed as prefix
FormSelectRx
Radix-based select with TanStack Form integration. Accessible dropdown with keyboard navigation.
FormSelectRxProps
| Prop | Type | Default | Description |
|---|---|---|---|
| form* | FormApi | — | TanStack Form instance |
| name* | keyof TFormData | — | Field name in form data |
| label* | string | — | Field label |
| options* | { value: string; label: string }[] | — | Select options |
| placeholder | string | — | Placeholder text |
Installation
import { FormSelectRx } from '@stackmates/ui-forms';Usage
With options
<FormSelectRx<FormData>
form={form}
name="framework"
label="Framework"
options={[
{ value: 'next', label: 'Next.js' },
{ value: 'remix', label: 'Remix' },
]}
/>Accessibility
- Built on Radix Select primitive — full keyboard support
- Arrow keys navigate options
- Type-ahead search within options
Combined Form Pattern
Demonstrates composing multiple form molecules into a cohesive form. All components share a single TanStack Form instance.
Priority deals appear at the top of your pipeline.
Installation
import { FormInput, FormSelect, FormMoneyInput, FormShell, FormTextarea, FormCheckbox, useForm } from '@stackmates/ui-forms';Usage
Complete form
const form = useForm({
defaultValues: { name: '', category: '', amount: 0 },
onSubmit: async ({ value }) => {
const result = await createEntityAction(value);
if (result.success) router.push('/list');
},
});
<FormShell form={form}>
<FormInput form={form} name="name" label="Name" />
<FormSelect form={form} name="category" label="Category" options={...} />
<FormMoneyInput form={form} name="amount" label="Value" />
<Button type="submit">Create</Button>
</FormShell>Accessibility
- Form uses action pattern for progressive enhancement
- All fields have labels and error display
- Tab order follows visual order
FormField
Layout wrapper molecule that composes a Label, help text, error messages, and any child input into an accessible form field. Automatically links label to input via htmlFor, wires aria-describedby for help/error text, and clones ARIA attributes onto the child element. Use this to build custom form fields from Raw atoms.
We will never share your email.
Select your country of residence.
Username is already taken, Must be at least 3 characters
FormFieldProps
| Prop | Type | Default | Description |
|---|---|---|---|
| label* | string | — | Label text displayed above the input |
| fieldId* | string | — | Unique ID for the input field (used for htmlFor and ARIA linking) |
| children* | ReactNode | — | The form input element (native or custom) to wrap |
| required | boolean | — | Whether the field is required (shows * indicator on label) |
| helpText | string | — | Optional help text displayed below the input |
| errors | string[] | [] | Array of error messages to display (from validation) |
| showErrors | boolean | true | Whether to show error messages |
| size | 'sm' | 'md' | 'lg' | 'md' | Size variant affecting label, help text, and error text size |
| className | string | — | Additional CSS classes for the wrapper div |
Installation
import { FormField } from '@stackmates/ui-forms';Usage
With RawInput
<FormField
label="Email"
fieldId="user-email"
required
helpText="We will never share your email."
>
<RawInput type="email" placeholder="you@example.com" />
</FormField>With validation errors
<FormField
label="Username"
fieldId="user-name"
required
errors={['Username is already taken']}
>
<RawInput placeholder="Enter username" />
</FormField>Accessibility
- Label linked to child input via htmlFor
- Help text linked via aria-describedby
- Error messages linked via aria-describedby
- Required fields show * indicator on label
- Clones aria-invalid onto child when errors present
FileUploadField
Drag-and-drop file upload molecule with click-to-browse fallback. Validates file type and size, displays selected files with progress bars, and supports removal. Built with Badge, Button, Progress, and a hidden FileInput atom.
Click to upload or drag and drop
PDF,DOCX,XLSX up to 10MB
FileUploadFieldProps
| Prop | Type | Default | Description |
|---|---|---|---|
| accept | string | '.pdf,.docx,.xlsx' | Accepted file types (comma-separated extensions) |
| multiple | boolean | false | Allow multiple file selection |
| maxSizeMB | number | 10 | Maximum file size in megabytes |
| onFilesSelected | (files: File[]) => void | — | Callback when files are selected or dropped |
| onFileRemove | (index: number) => void | — | Callback when a file is removed from the list |
| progress | number[] | [] | Upload progress (0-100) per file |
| error | string | — | Error message displayed below the drop zone |
| disabled | boolean | false | Disable the upload field |
Installation
import { FileUploadField } from '@stackmates/ui-forms';Usage
Basic upload
<FileUploadField
accept=".pdf,.docx"
maxSizeMB={5}
onFilesSelected={(files) => handleUpload(files)}
/>Multiple files with progress
<FileUploadField
accept=".csv,.xlsx"
multiple
maxSizeMB={20}
progress={[45, 100]}
onFilesSelected={handleFiles}
onFileRemove={handleRemove}
/>Accessibility
- Hidden file input with aria-label for screen readers
- Drop zone responds to keyboard focus via click handler
- Remove buttons have aria-label naming the file
- Progress shown with Badge text -- not color alone
NoteFormModal
Modal dialog for creating and editing notes. Composes Dialog, FormInput, FormTextarea, and FormCheckbox with TanStack Form. Includes unsaved-changes protection (confirmation dialog on close when dirty), pin toggle, and create/edit mode switching. Requires external open/onOpenChange state management.
Interactive demo requires server action — see usage examples below.
Installation
import { NoteFormModal } from '@stackmates/ui-forms';
import type { NoteFormData, NoteFormModalProps } from '@stackmates/ui-forms';Usage
Create mode
const [open, setOpen] = useState(false);
<NoteFormModal
open={open}
onOpenChange={setOpen}
mode="create"
entityName="Deal"
onSubmit={async (data) => {
await createNote(data);
}}
/>Edit mode with delete
<NoteFormModal
open={open}
onOpenChange={setOpen}
mode="edit"
initialData={{ title: 'Existing', content: 'Note body' }}
onSubmit={handleSave}
onDelete={handleDelete}
/>Accessibility
- Built on Radix Dialog -- focus trap and Escape to close
- ARIA labelled/described via DialogTitle and DialogDescription
- Unsaved changes dialog prevents accidental data loss
TaskFormModal
Modal dialog for creating and editing tasks with status, priority, dates, and estimated duration. Composes Dialog with FormInput, FormTextarea, FormSelect, and FormSelectRx via TanStack Form. Includes unsaved-changes protection, create/edit mode switching, and delete support. Requires external open/onOpenChange state management.
Interactive demo requires server action — see usage examples below.
Installation
import { TaskFormModal } from '@stackmates/ui-forms';
import type { TaskFormData, TaskFormModalProps } from '@stackmates/ui-forms';Usage
Create mode
const [open, setOpen] = useState(false);
<TaskFormModal
open={open}
onOpenChange={setOpen}
mode="create"
entityName="Deal"
onSubmit={async (data) => {
await createTask(data);
}}
/>Edit mode
<TaskFormModal
open={open}
onOpenChange={setOpen}
mode="edit"
initialData={{
title: 'Follow up call',
status: 'in_progress',
priority: 'high',
dueDate: '2026-03-15',
}}
onSubmit={handleSave}
onDelete={handleDelete}
/>Accessibility
- Built on Radix Dialog -- focus trap and Escape to close
- ARIA labelled/described via DialogTitle and DialogDescription
- Status and priority use FormSelectRx with keyboard navigation
- Unsaved changes dialog prevents accidental data loss