KQ-1674 · MARGIN/HELP
Margin Help
A form with a help rail: focusing a field glides its help card down the rail until it sits level with that field, on a spring whose landing is springiness — from springs.glide, which settles without overshoot, to springs.recoil, which lands with two bounces — while the card's content cross-fades in one cell, its measured height glides and its notch slides along the edge to keep pointing at the field. A field without help leaves the rail empty, an error opens as a band in the card on springs.snap, the Help switch glides the rail's width away, and below a 40rem container the help becomes an expandable line under each field. Every field's help is its description for assistive technology, the help link sits in the tab order right after its field, and Escape on the link returns to the field.
Wholesale account
Coldbrook Roasters · step 2 of 3
help · business name · 2 of 4 required
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/margin-help
Props
- fields
- MarginHelpField[]
- default defaultMarginHelpFields
- The form: { id, label, kind?, required?, placeholder?, options?, validate?, help?: { text, example?, link? } }.
- value / defaultValue
- Record<string, string>
- default — / defaultMarginHelpValues
- Answers by field id, controlled or initial.
- onChange
- (values, fieldId) => void
- default —
- Fires from the keystroke or choice that changed an answer.
- onSubmit
- (values) => void | Promise
- default —
- Send was pressed with every answer valid; a promise holds the button, a rejection keeps every answer.
- onFieldFocus
- (fieldId) => void
- default —
- The field the rail follows changed.
- onHelpLink
- (fieldId, link, event) => void
- default —
- A help link was followed; call event.preventDefault() to handle it yourself.
- showHelp / defaultShowHelp
- boolean
- default — / true
- Whether the rail shows, controlled or initial.
- onShowHelpChange
- (show) => void
- default —
- Fires from the Help switch.
- railWidth
- number
- default 240
- The help rail's width in px.
- cardStyle
- "card" | "note" | "margin"
- default "card"
- A raised card with a notch, a tinted note with an accent edge, or a bare margin note.
- springiness
- number
- default 0.35
- How the card lands, 0 (glide, no overshoot) to 1 (recoil, two bounces).
- accent
- string
- default "var(--accent-bright)"
- Links, the focused label, the note's edge and Send — any CSS colour; buttons take its hue at a fixed lightness so white text reads in both themes.
- heading
- string
- default "Wholesale account"
- The form's heading and accessible name.
- subheading
- string
- default "Coldbrook Roasters · step 2 of 3"
- The line under the heading.
- submitLabel
- string
- default "Send application"
- The submit button's label.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading shows skeleton fields; error shows Try again.
- onRetry
- () => void
- default —
- Try again was pressed after the form failed to load.
- height
- number
- default 540
- The form's height in px; the fields and the rail scroll together inside it.
- disabled
- boolean
- default false
- Nothing can be typed or sent.
- className
- string
- default —
- Classes for the form.
| Prop | Type | Default | Description |
|---|---|---|---|
| fields | MarginHelpField[] | defaultMarginHelpFields | The form: { id, label, kind?, required?, placeholder?, options?, validate?, help?: { text, example?, link? } }. |
| value / defaultValue | Record<string, string> | — / defaultMarginHelpValues | Answers by field id, controlled or initial. |
| onChange | (values, fieldId) => void | — | Fires from the keystroke or choice that changed an answer. |
| onSubmit | (values) => void | Promise | — | Send was pressed with every answer valid; a promise holds the button, a rejection keeps every answer. |
| onFieldFocus | (fieldId) => void | — | The field the rail follows changed. |
| onHelpLink | (fieldId, link, event) => void | — | A help link was followed; call event.preventDefault() to handle it yourself. |
| showHelp / defaultShowHelp | boolean | — / true | Whether the rail shows, controlled or initial. |
| onShowHelpChange | (show) => void | — | Fires from the Help switch. |
| railWidth | number | 240 | The help rail's width in px. |
| cardStyle | "card" | "note" | "margin" | "card" | A raised card with a notch, a tinted note with an accent edge, or a bare margin note. |
| springiness | number | 0.35 | How the card lands, 0 (glide, no overshoot) to 1 (recoil, two bounces). |
| accent | string | "var(--accent-bright)" | Links, the focused label, the note's edge and Send — any CSS colour; buttons take its hue at a fixed lightness so white text reads in both themes. |
| heading | string | "Wholesale account" | The form's heading and accessible name. |
| subheading | string | "Coldbrook Roasters · step 2 of 3" | The line under the heading. |
| submitLabel | string | "Send application" | The submit button's label. |
| status | "ready" | "loading" | "error" | "ready" | Loading shows skeleton fields; error shows Try again. |
| onRetry | () => void | — | Try again was pressed after the form failed to load. |
| height | number | 540 | The form's height in px; the fields and the rail scroll together inside it. |
| disabled | boolean | false | Nothing can be typed or sent. |
| className | string | — | Classes for the form. |