{
  "name": "Form",
  "summary": "A form that validates, reports errors where the user can act on them, and survives a failed submit.",
  "useWhen": [
    "collecting more than one field from the user before an action takes effect",
    "the submit can fail server-side — which is every real form"
  ],
  "composes": [
    "Input",
    "Select",
    "Checkbox",
    "CheckboxGroup",
    "TreeView",
    "Fieldset",
    "Radio",
    "RadioGroup",
    "Toggletip",
    "Toggle",
    "DateTimePicker",
    "Calendar",
    "PhoneInput",
    "NumberInput",
    "Button",
    "ButtonGroup",
    "Alert",
    "Divider",
    "InlineEdit",
    "SelectableTile",
    "Grid"
  ],
  "structure": [
    "Fields in a single column. Two columns only for genuinely paired values — a Grid with `columns={2}` and `align=\"start\"`, so a field with an error does not stretch its neighbour; its default `collapse=\"mobile\"` puts them one above the other below the mobile breakpoint.",
    "Each field carries its own label and, when invalid, its own errorMessage — the message sits with the control it is about.",
    "Guidance needed to fill a field in goes in its `description`, always visible. An explanation most people will not need — why a limit exists, where to read more — goes in a Toggletip right after the label text, where it can hold a link.",
    "Related fields that answer one question — an address, a pair of limits, a set of checkboxes — sit in a Fieldset, CheckboxGroup or RadioGroup, so the question is announced with each field and a group-level error has somewhere to live.",
    "Choices that form a hierarchy — folders, teams within departments — are one TreeView with `selectionMode=\"multiple\"` and an `aria-label` naming the question, not Checkboxes indented by hand. The tree announces each choice's level and place; indentation alone announces nothing.",
    "A moment — when a run starts, when a window opens — is one DateTimePicker, not a DatePicker beside a TimeField: one value, one error, and a bound such as \"not before now\" checked against the date and the time together through `minValue`. Its `description` names the timezone. A date and a time that are separate, optional choices — a due date with an optional reminder — stay two fields.",
    "A day that is a dialog's whole question — the day a paused agent resumes — is a Calendar in the Modal, bounded by `minValue` and `maxValue`, with the confirming button disabled until a day is picked and naming it: \"Pause until 3 October\". A date among other fields, or one the person knows, stays a DatePicker.",
    "An error summary Alert above the form after a failed submit, listing each invalid field as a link to it.",
    "The actions in a ButtonGroup at the end: cancel first, the submit Button last — the primary last, as every action row in the system has it, so it is reached after the options it is chosen over and sits at the end of the row. `stack` on a form a phone will fill.",
    "One value on a page that is read far more often than changed — a record's name or description — is an InlineEdit where it stands, not a form of one field: Edit, then Save or Cancel, with `validate` for its rule. Several values that belong together are still a form with one submit.",
    "A choice whose options each need a sentence to be chosen well — a plan, a trigger — is the same RadioGroup or CheckboxGroup with SelectableTiles for options: real radios or checkboxes the size of a card, in a grid, the sentence read after each name. Links go beside the group, never inside a tile."
  ],
  "states": {
    "loading": {
      "must": "Disable the submit Button and say what is happening on it — \"Saving…\" — rather than swapping the form for a spinner.",
      "why": "the user needs to see what they typed while it is being saved, and a disabled control with unchanged text reads as broken",
      "a11y": "announce the outcome in a live region; a disabled button cannot hold focus to be read"
    },
    "empty": {
      "must": "A form's empty state is its initial state: no field shows an error before the user has touched it.",
      "why": "validating on mount marks a form the user has not filled in as wrong, which teaches them to ignore the errors"
    },
    "error": {
      "must": "Two levels, both required. Per-field `errorMessage` with `isInvalid`, plus an error summary Alert naming every failed field. Never clear what the user typed.",
      "why": "the summary is how a screen-reader or zoomed user finds the failures without hunting the whole form; the per-field message is how they fix each one",
      "a11y": "move focus to the summary after a failed submit — Alert's error intent already carries role=\"alert\""
    },
    "partial": {
      "must": "When the server accepts some fields and rejects others, keep every value and mark only what failed.",
      "why": "re-entering accepted data is the fastest way to lose a user mid-form"
    }
  },
  "propsUsed": {
    "InlineEdit": [
      "label",
      "editLabel",
      "value",
      "onSave",
      "validate"
    ],
    "SelectableTile": [
      "value",
      "title",
      "description"
    ],
    "NumberInput": [
      "label",
      "minValue",
      "maxValue",
      "formatOptions",
      "isInvalid",
      "errorMessage"
    ],
    "Input": [
      "label",
      "description",
      "errorMessage",
      "isInvalid",
      "isRequired"
    ],
    "Select": [
      "label",
      "description",
      "errorMessage",
      "isInvalid"
    ],
    "RadioGroup": [
      "label",
      "name",
      "value",
      "onChange",
      "isInvalid",
      "errorMessage",
      "isRequired"
    ],
    "CheckboxGroup": [
      "label",
      "value",
      "onChange",
      "isInvalid",
      "errorMessage",
      "isRequired"
    ],
    "Fieldset": [
      "label",
      "isInvalid",
      "errorMessage"
    ],
    "Checkbox": [
      "isDisabled"
    ],
    "Button": [
      "variant",
      "size",
      "isDisabled"
    ],
    "ButtonGroup": [
      "stack"
    ],
    "Alert": [
      "intent",
      "title",
      "actions"
    ],
    "Toggletip": [
      "aria-label",
      "size"
    ],
    "TreeView": [
      "aria-label",
      "items",
      "selectionMode",
      "selectedKeys",
      "onSelectionChange",
      "defaultExpandedKeys"
    ],
    "Grid": [
      "columns",
      "align"
    ],
    "DateTimePicker": [
      "label",
      "description",
      "value",
      "onChange",
      "minValue",
      "isRequired"
    ],
    "Calendar": [
      "label",
      "description",
      "value",
      "onChange",
      "minValue",
      "maxValue"
    ]
  },
  "variantsUsed": {
    "Alert": {
      "intent": [
        "error"
      ]
    },
    "Button": {
      "variant": [
        "primary-brand",
        "tertiary"
      ]
    }
  },
  "a11y": {
    "requires": [
      "a visible `label` on every field, or `aria-label` where the label is genuinely elsewhere",
      "`isInvalid` alongside `errorMessage` — the message alone styles the field without announcing it as invalid",
      "focus moved to the error summary after a failed submit",
      "`isRequired` on required fields rather than an asterisk in the label text",
      "a group-level error on the CheckboxGroup, RadioGroup or Fieldset it is about — \"choose at least one\" belongs to the set, not to its first box"
    ],
    "notes": [
      "Validate on blur and on submit, not on every keystroke: an error that appears while the user is still typing the value is wrong more often than right."
    ]
  },
  "antiPatterns": [
    {
      "dont": "clearing fields after a failed submit",
      "why": "it punishes the user for the server's answer"
    },
    {
      "dont": "an error summary with no per-field messages, or per-field with no summary",
      "why": "each solves a different problem — finding the failures, and fixing one"
    },
    {
      "dont": "a disabled submit Button as the only validation feedback",
      "why": "the user is told no and never told why; a disabled button also takes no focus, so it cannot explain itself"
    },
    {
      "dont": "placeholder text in place of a label",
      "why": "it disappears on focus, which is exactly when it is needed, and it is not reliably announced"
    },
    {
      "dont": "a Toggle inside a form that is submitted",
      "do": "Checkbox",
      "why": "a switch says the change already took effect"
    },
    {
      "dont": "a row of Checkboxes under a heading, with the error in a paragraph beneath",
      "do": "<CheckboxGroup label=\"…\" isRequired isInvalid errorMessage=\"…\">",
      "why": "the heading is not announced with the boxes and the error is attached to none of them"
    },
    {
      "dont": "a Tooltip beside a label holding a link or the rule the field is validated against",
      "do": "`description` for the rule; a Toggletip for the optional explanation and its link",
      "why": "a tooltip is unreachable by keyboard and invisible on touch, so required guidance in one is found by failing"
    },
    {
      "dont": "a CheckboxGroup whose boxes are indented with margins to show which folder each file is in",
      "do": "<TreeView aria-label=\"…\" selectionMode=\"multiple\" items={…} />",
      "why": "the indentation is visual only: a screen reader hears one flat list, and there is no way to close a branch that is not wanted"
    },
    {
      "dont": "a record's name as a button that turns into a field and saves on blur",
      "do": "<InlineEdit label=\"Name\" value={name} onSave={save} />",
      "why": "the value is announced as a button, and tabbing away to check something commits a half-typed value"
    },
    {
      "dont": "a DatePicker and a TimeField for one moment, with the TimeField's minimum moved when the date is today",
      "do": "<DateTimePicker minValue={now} description=\"Workspace time, UTC.\" … />",
      "why": "the rule spans two fields, so each form writes it again and gets an edge wrong — midnight, a date typed after the time"
    },
    {
      "dont": "a DatePicker as the only field of a Modal",
      "do": "<Calendar label=\"Resume on\" minValue={tomorrow} … />",
      "why": "its calendar opens as a popover over the dialog, a second layer for the dialog's one question"
    }
  ]
}
