diff --git a/docs/hooks/hooks.md b/docs/hooks/hooks.md
index 99eba239b..f4e43122b 100644
--- a/docs/hooks/hooks.md
+++ b/docs/hooks/hooks.md
@@ -96,11 +96,20 @@ Wrap fields in `SDKFormProvider` and they pick up form state from context automa
```tsx
import { SDKFormProvider } from '@gusto/embedded-react-sdk'
-;
-
-
-
-
+
+function EmployeeFormSection() {
+ // ...employeeDetails from useEmployeeDetailsForm
+
+ const { Fields } = employeeDetails.form
+
+ return (
+
+
+
+
+
+ )
+}
```
Fields inside an `SDKFormProvider` don't need the `formHookResult` prop — the provider injects form state via React context. This is convenient when all fields from a single hook are grouped together.
@@ -116,7 +125,35 @@ Both approaches produce identical validation, API payloads, and behavior. The di
| **Interleaving** | Fields from different hooks can be placed in any order | Fields must stay within their provider boundary |
| **API error syncing** | Handled automatically per-field | Handled automatically via the provider |
-You can use different approaches for different hooks on the same page — for example, `SDKFormProvider` for one hook's fields that are grouped together, and `formHookResult` props for another hook's fields that are scattered. See [Composing Multiple Hooks](#composing-multiple-hooks) for examples.
+#### Side-by-side: two hooks on one screen
+
+The same `employeeDetails` + `compensation` form, written each way:
+
+```tsx
+// Option A: formHookResult prop — fields can be interleaved freely
+const EmployeeDetailsFields = employeeDetails.form.Fields
+const CompensationFields = compensation.form.Fields
+
+
+
+
+
+```
+
+```tsx
+// Option B: SDKFormProvider — one wrapper per hook, fields stay grouped
+
+
+
+
+
+
+
+
+
+```
+
+You can also mix approaches on the same page — for example, `SDKFormProvider` for one hook's fields that are grouped together, and `formHookResult` props for another hook's fields that are scattered. See [Composing Multiple Hooks](#composing-multiple-hooks) for the full submit-handler wiring around either layout.
> **Avoid passing `formHookResult` to fields via props that are already inside an `SDKFormProvider`.** When both are present on the same field, the prop takes precedence and the provider's context is ignored, which may lead to unexpected behavior.
@@ -155,6 +192,7 @@ function MyCustomTextInput(props: TextInputProps) {
)
}
+// Then in your form:
;
```
-If you omit `validationMessages`, validation still runs but no message is displayed — the field is marked as invalid without explanatory text.
+If you omit `validationMessages`, validation still runs and the field is marked as invalid, but the displayed text falls back to the raw error code (e.g., `REQUIRED`, `INVALID_EMAIL`, `INVALID_AMOUNT`). Always supply `validationMessages` for production UI so partners control the user-facing copy.
Error codes for each hook are exported alongside the hook:
@@ -847,19 +885,3 @@ Use this when you need to:
- Access form state like `isDirty`, `isValid`, or `dirtyFields`
In most cases the built-in Fields, `onSubmit`, and `getFormSubmissionValues` are sufficient. Reach for `hookFormInternals` only when you need fine-grained form control that the hook doesn't expose directly.
-
----
-
-## Advanced: Fields Metadata
-
-Each hook exposes `form.fieldsMetadata` — an object keyed by field name with metadata about each field's current state. The field components consume this automatically under the hood to determine required/disabled states and populate select options, so you typically don't need to interact with it directly.
-
-If you're building fully custom field UI, you can read this metadata yourself:
-
-```tsx
-const { fieldsMetadata } = employeeDetails.form
-
-if (fieldsMetadata.email.isRequired) {
- // Show a required indicator in your custom UI
-}
-```
diff --git a/docs/hooks/useCompensationForm.md b/docs/hooks/useCompensationForm.md
index 6f53efe7f..775ca801b 100644
--- a/docs/hooks/useCompensationForm.md
+++ b/docs/hooks/useCompensationForm.md
@@ -29,35 +29,35 @@ import { useCompensationForm, SDKFormProvider } from '@gusto/embedded-react-sdk'
### Configurable Required Fields
-The compensation schema declares requiredness rules per field. Fields default to **always required** unless configured otherwise:
-
-| Field | Rule | Required on create | Required on update | Partner-configurable? |
-| ----------------------- | ---------- | ------------------ | ------------------ | --------------------- |
-| `jobTitle` | `'create'` | Yes | No | Yes (on update) |
-| `flsaStatus` | `'create'` | Yes | No | Yes (on update) |
-| `paymentUnit` | `'create'` | Yes | No | Yes (on update) |
-| `rate` | `'create'` | Yes | No | Yes (on update) |
-| `startDate` | `'create'` | Yes | No | Yes (on update) |
-| `adjustForMinimumWage` | (unlisted) | Yes | Yes | No |
-| `stateWcCovered` | (unlisted) | Yes | Yes | No |
-| `twoPercentShareholder` | (unlisted) | Yes | Yes | No |
-| `minimumWageId` | predicate | When toggle is on | When toggle is on | No |
-| `stateWcClassCode` | predicate | When WC is covered | When WC is covered | No |
-
-`optionalFieldsToRequire` lets you override fields that are **optional** in a given mode to be required. The type is derived from the schema configuration:
+Each compensation field has a default required-ness based on form mode (create vs. update). Fields default to **always required** unless configured otherwise:
+
+| Field | Required on create | Required on update | Partner-configurable? |
+| ----------------------- | ------------------ | ------------------ | --------------------- |
+| `jobTitle` | Yes | No | Yes (on update) |
+| `flsaStatus` | Yes | No | Yes (on update) |
+| `paymentUnit` | Yes | No | Yes (on update) |
+| `rate` | Yes | No | Yes (on update) |
+| `startDate` | Yes | No | Yes (on update) |
+| `adjustForMinimumWage` | Yes | Yes | No |
+| `stateWcCovered` | Yes | Yes | No |
+| `twoPercentShareholder` | Yes | Yes | No |
+| `minimumWageId` | When toggle is on | When toggle is on | No |
+| `stateWcClassCode` | When WC is covered | When WC is covered | No |
+
+`optionalFieldsToRequire` lets you override fields that are **optional** in a given mode to be required. TypeScript constrains which fields can be promoted per mode:
```typescript
type CompensationOptionalFieldsToRequire = {
- create?: never[] // all 'create' fields are already required on create
+ create?: never[] // these fields are already required on create
update?: Array<'jobTitle' | 'flsaStatus' | 'paymentUnit' | 'rate' | 'startDate'>
}
```
-Only the `update` key is useful for compensation — it lets you require fields in update mode that would otherwise be optional. The `create` key has no configurable fields because all `'create'`-scoped fields are already required on create.
+Only the `update` key is useful for compensation — it lets you require fields in update mode that would otherwise be optional. The `create` key has no configurable fields because every field listed above is already required on create.
-`startDate` requirements are ignored when `withStartDateField` is `false` (the field is excluded from the schema entirely).
+`startDate` requirements are ignored when `withStartDateField` is `false` (the field is omitted from the form entirely).
-Cross-field validations (`minimumWageId` when `adjustForMinimumWage` is `true`, workers' comp fields in WA) are always enforced by the schema regardless of `optionalFieldsToRequire`.
+Cross-field rules (`minimumWageId` when `adjustForMinimumWage` is `true`, workers' comp fields in WA) are always enforced regardless of `optionalFieldsToRequire`.
```tsx
useCompensationForm({
@@ -312,7 +312,7 @@ This field is automatically **disabled** when the FLSA status is Commission Only
validationMessages={{
REQUIRED: 'Amount is a required field',
RATE_MINIMUM: 'Amount must be at least $1.00',
- RATE_EXEMPT_THRESHOLD: `FLSA Exempt employees must meet salary threshold of $${FLSA_OVERTIME_SALARY_LIMIT}/year`,
+ RATE_EXEMPT_THRESHOLD: 'FLSA Exempt employees must meet salary threshold of $35,568/year',
}}
/>
```
diff --git a/docs/hooks/useEmployeeDetailsForm.md b/docs/hooks/useEmployeeDetailsForm.md
index 4ceb7f6e6..62cb2fce7 100644
--- a/docs/hooks/useEmployeeDetailsForm.md
+++ b/docs/hooks/useEmployeeDetailsForm.md
@@ -158,7 +158,7 @@ const EmployeeDetailsErrorCodes = {
### Fields.FirstName
-Text input for the employee's first name. Validates against `NAME_REGEX` to reject special characters.
+Text input for the employee's first name. Validates that the value contains only allowed name characters (letters, spaces, hyphens, and apostrophes).
| Prop | Type | Required |
| -------------------- | -------------------------------------------- | -------- |
@@ -208,7 +208,7 @@ No validation codes — this field is always optional.
### Fields.LastName
-Text input for the employee's last name. Validates against `NAME_REGEX` to reject special characters.
+Text input for the employee's last name. Validates that the value contains only allowed name characters (letters, spaces, hyphens, and apostrophes).
| Prop | Type | Required |
| -------------------- | -------------------------------------------- | -------- |
@@ -310,7 +310,7 @@ Text input for the employee's Social Security number. Automatically formats inpu
| ------------- | -------------------------------------------- |
| `INVALID_SSN` | Value does not match the expected SSN format |
-The `fieldsMetadata.ssn.hasRedactedValue` flag indicates whether the employee already has an SSN on file. When `true`, the field renders with a masked placeholder (e.g., `•••-••-1234`). If the field is included in `requiredFields` but `hasSsn` is already `true` on the employee, the requirement is automatically waived.
+The masked placeholder appears as `•••-••-1234`. When the employee already has an SSN on record, the `REQUIRED` rule is automatically waived even if `ssn` is listed in `requiredFields`.
```tsx
` | No | — | Pre-fill form values. Server data takes precedence when editing an existing work address. |
-| `validationMode` | `'onSubmit' \| 'onBlur' \| 'onChange' \| 'onTouched' \| 'all'` | No | `'onSubmit'` | When validation runs. Passed through to react-hook-form. |
-| `shouldFocusError` | `boolean` | No | `true` | Auto-focus the first invalid field on submit. Set to `false` when using `composeSubmitHandler`. |
+| Prop | Type | Required | Default | Description |
+| ------------------------ | ------------------------------------------------------------------------------------ | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `companyId` | `string` | Yes | — | The UUID of the company. Used to fetch available locations. |
+| `employeeId` | `string` | Yes | — | The UUID of the employee. For composed create flows where the id isn't known until a prior form submits, pass `''` here and supply the real id at submit time via `WorkAddressSubmitOptions.employeeId`. |
+| `workAddressUuid` | `string` | No | — | When set, the hook is in update mode and loads that work address row. When omitted, the hook is in create mode. Use `useCurrentWorkAddressForm` to auto-resolve the employee's active row. |
+| `withEffectiveDateField` | `boolean` | No | `true` | Whether to include the effective date field. When `false`, pass effective date via `onSubmit` options instead. |
+| `requiredFields` | `WorkAddressField[] \| { create?: WorkAddressField[], update?: WorkAddressField[] }` | No | — | Additional fields to make required beyond API defaults. A flat array applies to both modes; an object targets specific modes. |
+| `defaultValues` | `Partial` | No | — | Pre-fill form values. Server data takes precedence when editing an existing work address. |
+| `validationMode` | `'onSubmit' \| 'onBlur' \| 'onChange' \| 'onTouched' \| 'all'` | No | `'onSubmit'` | When validation runs. Passed through to react-hook-form. |
+| `shouldFocusError` | `boolean` | No | `true` | Auto-focus the first invalid field on submit. Set to `false` when using `composeSubmitHandler`. |
### WorkAddressField
@@ -108,7 +108,7 @@ The hook returns a discriminated union on `isLoading`.
### Mode detection
-The hook is in update mode when `workAddressUuid` is provided (the row is fetched via GET `/v1/work_addresses/{work_address_uuid}`) and in create mode otherwise.
+The hook is in update mode when `workAddressUuid` is provided and in create mode otherwise.
### Submit callbacks