Design System

Form Molecules

ImportCC

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

PropTypeDefaultDescription
form*FormApiTanStack Form instance
name*keyof TFormDataField name in form data
label*stringCheckbox label text
helpTextstringHelp text below checkbox

Installation

typescript
import { FormCheckbox } from '@stackmates/ui-forms';

Usage

With description

tsx
<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

PropTypeDefaultDescription
form*FormApiTanStack Form instance
name*keyof TFormDataField name in form data
label*stringField label
currencystring'USD'Currency code
placeholderstringPlaceholder text

Installation

typescript
import { FormMoneyInput } from '@stackmates/ui-forms';

Usage

Basic

tsx
<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

PropTypeDefaultDescription
form*FormApiTanStack Form instance
name*keyof TFormDataField name in form data
label*stringField label
options*{ value: string; label: string }[]Select options
placeholderstringPlaceholder text

Installation

typescript
import { FormSelectRx } from '@stackmates/ui-forms';

Usage

With options

tsx
<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.

PropsContribute this section

Installation

typescript
import { FormInput, FormSelect, FormMoneyInput, FormShell, FormTextarea, FormCheckbox, useForm } from '@stackmates/ui-forms';

Usage

Complete form

tsx
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.

FormFieldProps

PropTypeDefaultDescription
label*stringLabel text displayed above the input
fieldId*stringUnique ID for the input field (used for htmlFor and ARIA linking)
children*ReactNodeThe form input element (native or custom) to wrap
requiredbooleanWhether the field is required (shows * indicator on label)
helpTextstringOptional help text displayed below the input
errorsstring[][]Array of error messages to display (from validation)
showErrorsbooleantrueWhether to show error messages
size'sm' | 'md' | 'lg''md'Size variant affecting label, help text, and error text size
classNamestringAdditional CSS classes for the wrapper div

Installation

typescript
import { FormField } from '@stackmates/ui-forms';

Usage

With RawInput

tsx
<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

tsx
<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

PropTypeDefaultDescription
acceptstring'.pdf,.docx,.xlsx'Accepted file types (comma-separated extensions)
multiplebooleanfalseAllow multiple file selection
maxSizeMBnumber10Maximum file size in megabytes
onFilesSelected(files: File[]) => voidCallback when files are selected or dropped
onFileRemove(index: number) => voidCallback when a file is removed from the list
progressnumber[][]Upload progress (0-100) per file
errorstringError message displayed below the drop zone
disabledbooleanfalseDisable the upload field

Installation

typescript
import { FileUploadField } from '@stackmates/ui-forms';

Usage

Basic upload

tsx
<FileUploadField
  accept=".pdf,.docx"
  maxSizeMB={5}
  onFilesSelected={(files) => handleUpload(files)}
/>

Multiple files with progress

tsx
<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.

PropsContribute this section

Installation

typescript
import { NoteFormModal } from '@stackmates/ui-forms';
import type { NoteFormData, NoteFormModalProps } from '@stackmates/ui-forms';

Usage

Create mode

tsx
const [open, setOpen] = useState(false);

<NoteFormModal
  open={open}
  onOpenChange={setOpen}
  mode="create"
  entityName="Deal"
  onSubmit={async (data) => {
    await createNote(data);
  }}
/>

Edit mode with delete

tsx
<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.

PropsContribute this section

Installation

typescript
import { TaskFormModal } from '@stackmates/ui-forms';
import type { TaskFormData, TaskFormModalProps } from '@stackmates/ui-forms';

Usage

Create mode

tsx
const [open, setOpen] = useState(false);

<TaskFormModal
  open={open}
  onOpenChange={setOpen}
  mode="create"
  entityName="Deal"
  onSubmit={async (data) => {
    await createTask(data);
  }}
/>

Edit mode

tsx
<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