@maltjoy/mcp-server 0.4.0 → 0.6.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -3
  3. package/dist/doc/VJoyAdminBanner/guideline.md +284 -0
  4. package/dist/doc/VJoyAvailability/guideline.md +352 -0
  5. package/dist/doc/VJoyAvatar/guideline.md +192 -0
  6. package/dist/doc/VJoyAvatarsList/guideline.md +328 -0
  7. package/dist/doc/VJoyBadge/guideline.md +169 -0
  8. package/dist/doc/VJoyBadgeLevel/guideline.md +166 -0
  9. package/dist/doc/VJoyBlockSkeleton/guideline.md +150 -0
  10. package/dist/doc/VJoyBottomSheet/guideline.md +345 -0
  11. package/dist/doc/VJoyBottomSheetTrigger/guideline.md +156 -0
  12. package/dist/doc/VJoyButton/guideline.md +371 -0
  13. package/dist/doc/VJoyButton/metadata.json +0 -16
  14. package/dist/doc/VJoyButtonCard/guideline.md +384 -0
  15. package/dist/doc/VJoyCheckbox/guideline.md +317 -0
  16. package/dist/doc/VJoyCheckboxCard/guideline.md +381 -0
  17. package/dist/doc/VJoyCheckboxGroup/guideline.md +363 -0
  18. package/dist/doc/VJoyCheckboxGroup/metadata.json +7 -7
  19. package/dist/doc/VJoyCollapse/guideline.md +362 -0
  20. package/dist/doc/VJoyCollapseItem/guideline.md +394 -0
  21. package/dist/doc/VJoyCompanyAvatar/guideline.md +162 -0
  22. package/dist/doc/VJoyDialog/guideline.md +425 -0
  23. package/dist/doc/VJoyDialogTrigger/guideline.md +186 -0
  24. package/dist/doc/VJoyDot/guideline.md +177 -0
  25. package/dist/doc/VJoyDrawer/guideline.md +392 -0
  26. package/dist/doc/VJoyDrawerTrigger/guideline.md +145 -0
  27. package/dist/doc/VJoyDropdown/guideline.md +393 -0
  28. package/dist/doc/VJoyDropdown/metadata.json +6 -6
  29. package/dist/doc/VJoyDropdownList/guideline.md +396 -0
  30. package/dist/doc/VJoyDropdownList/metadata.json +7 -7
  31. package/dist/doc/VJoyDropzone/guideline.md +390 -0
  32. package/dist/doc/VJoyDropzone/metadata.json +36 -1
  33. package/dist/doc/VJoyFilterBar/guideline.md +419 -0
  34. package/dist/doc/VJoyFilterBarButton/guideline.md +359 -0
  35. package/dist/doc/VJoyFloatingActionsBar/guideline.md +372 -0
  36. package/dist/doc/VJoyFormError/guideline.md +325 -0
  37. package/dist/doc/VJoyFormError/metadata.json +0 -14
  38. package/dist/doc/VJoyFormFieldSkeleton/guideline.md +159 -0
  39. package/dist/doc/VJoyFormGroup/guideline.md +285 -0
  40. package/dist/doc/VJoyFunnel/guideline.md +349 -0
  41. package/dist/doc/VJoyFunnelFooter/guideline.md +178 -0
  42. package/dist/doc/VJoyFunnelHeader/guideline.md +204 -0
  43. package/dist/doc/VJoyHighlight/guideline.md +169 -0
  44. package/dist/doc/VJoyIcon/JoyIcon.types.ts +1 -1
  45. package/dist/doc/VJoyIcon/guideline.md +168 -0
  46. package/dist/doc/VJoyIcon/metadata.json +0 -1
  47. package/dist/doc/VJoyIconButton/guideline.md +169 -0
  48. package/dist/doc/VJoyIconButton/metadata.json +1 -1
  49. package/dist/doc/VJoyIndicator/guideline.md +168 -0
  50. package/dist/doc/VJoyIndicators/guideline.md +183 -0
  51. package/dist/doc/VJoyInput/JoyInput.types.ts +1 -6
  52. package/dist/doc/VJoyInput/guideline.md +401 -0
  53. package/dist/doc/VJoyInput/metadata.json +5 -14
  54. package/dist/doc/VJoyInputDigit/guideline.md +381 -0
  55. package/dist/doc/VJoyInputNumber/guideline.md +414 -0
  56. package/dist/doc/VJoyInputNumber/metadata.json +22 -17
  57. package/dist/doc/VJoyInputPassword/guideline.md +379 -0
  58. package/dist/doc/VJoyInputPassword/metadata.json +0 -14
  59. package/dist/doc/VJoyLabel/guideline.md +200 -0
  60. package/dist/doc/VJoyLink/guideline.md +360 -0
  61. package/dist/doc/VJoyListItem/guideline.md +335 -0
  62. package/dist/doc/VJoyMenu/guideline.md +304 -0
  63. package/dist/doc/VJoyMenu/metadata.json +21 -21
  64. package/dist/doc/VJoyMenuItem/guideline.md +352 -0
  65. package/dist/doc/VJoyPagination/guideline.md +355 -0
  66. package/dist/doc/VJoyPanel/guideline.md +238 -0
  67. package/dist/doc/VJoyPanelSection/guideline.md +160 -0
  68. package/dist/doc/VJoyPasswordRequirement/guideline.md +336 -0
  69. package/dist/doc/VJoyPasswordRequirement/metadata.json +1 -1
  70. package/dist/doc/VJoyProductTour/JoyProductTour.types.ts +0 -1
  71. package/dist/doc/VJoyProductTour/guideline.md +455 -0
  72. package/dist/doc/VJoyProductTour/metadata.json +1 -1
  73. package/dist/doc/VJoyProductTourTrigger/guideline.md +167 -0
  74. package/dist/doc/VJoyProgressBar/guideline.md +348 -0
  75. package/dist/doc/VJoyRadio/guideline.md +346 -0
  76. package/dist/doc/VJoyRadioGroup/guideline.md +331 -0
  77. package/dist/doc/VJoyRatingStars/guideline.md +306 -0
  78. package/dist/doc/VJoyScreenLoader/guideline.md +172 -0
  79. package/dist/doc/VJoySelect/guideline.md +396 -0
  80. package/dist/doc/VJoySelectableItem/guideline.md +351 -0
  81. package/dist/doc/VJoySelectableItemGroup/guideline.md +370 -0
  82. package/dist/doc/VJoySeparator/guideline.md +161 -0
  83. package/dist/doc/VJoySnackbar/guideline.md +402 -0
  84. package/dist/doc/VJoySpinner/guideline.md +168 -0
  85. package/dist/doc/VJoyStep/guideline.md +182 -0
  86. package/dist/doc/VJoyStepper/guideline.md +390 -0
  87. package/dist/doc/VJoyTab/guideline.md +298 -0
  88. package/dist/doc/VJoyTabs/guideline.md +353 -0
  89. package/dist/doc/VJoyTag/guideline.md +226 -0
  90. package/dist/doc/VJoyTagsInput/guideline.md +387 -0
  91. package/dist/doc/VJoyTagsList/guideline.md +311 -0
  92. package/dist/doc/VJoyTagsList/metadata.json +27 -27
  93. package/dist/doc/VJoyTemplate/guideline.md +366 -0
  94. package/dist/doc/VJoyText/guideline.md +175 -0
  95. package/dist/doc/VJoyText/metadata.json +1 -1
  96. package/dist/doc/VJoyTextarea/guideline.md +378 -0
  97. package/dist/doc/VJoyTitleBrand/guideline.md +155 -0
  98. package/dist/doc/VJoyToggle/guideline.md +327 -0
  99. package/dist/doc/VJoyTooltip/guideline.md +322 -0
  100. package/dist/doc/VJoyUserCard/guideline.md +268 -0
  101. package/dist/doc/VJoyUserCard/metadata.json +3 -17
  102. package/dist/doc/VJoyWalkthrough/guideline.md +351 -0
  103. package/dist/doc/VJoyWalkthroughTrigger/guideline.md +171 -0
  104. package/dist/doc/VJoyWrapper/guideline.md +173 -0
  105. package/dist/doc/css-classes.json +1 -15
  106. package/dist/doc/design-tokens.json +1 -0
  107. package/dist/doc/index.json +20 -30
  108. package/dist/index.js +22 -2
  109. package/dist/index.js.map +1 -1
  110. package/dist/tools/get-component-guideline.d.ts +30 -0
  111. package/dist/tools/get-component-guideline.d.ts.map +1 -0
  112. package/dist/tools/get-component-guideline.js +51 -0
  113. package/dist/tools/get-component-guideline.js.map +1 -0
  114. package/dist/tools/get-documentation-page.d.ts +22 -0
  115. package/dist/tools/get-documentation-page.d.ts.map +1 -0
  116. package/dist/tools/get-documentation-page.js +21 -0
  117. package/dist/tools/get-documentation-page.js.map +1 -0
  118. package/dist/tools/list-documentation-pages.d.ts +22 -0
  119. package/dist/tools/list-documentation-pages.d.ts.map +1 -0
  120. package/dist/tools/list-documentation-pages.js +17 -0
  121. package/dist/tools/list-documentation-pages.js.map +1 -0
  122. package/dist/tools/search-documentation.d.ts +41 -0
  123. package/dist/tools/search-documentation.d.ts.map +1 -0
  124. package/dist/tools/search-documentation.js +25 -0
  125. package/dist/tools/search-documentation.js.map +1 -0
  126. package/dist/zeroheight-parser.d.ts +27 -0
  127. package/dist/zeroheight-parser.d.ts.map +1 -0
  128. package/dist/zeroheight-parser.js +77 -0
  129. package/dist/zeroheight-parser.js.map +1 -0
  130. package/package.json +10 -10
  131. package/dist/doc/VJoyMultiCheckbox/metadata.json +0 -332
@@ -26,15 +26,6 @@
26
26
  "true"
27
27
  ]
28
28
  },
29
- {
30
- "name": "noHtmlErrorText",
31
- "type": "string | undefined",
32
- "required": false,
33
- "description": "Error message to display. If you need a list of errors, use the slot instead",
34
- "possibleValues": [
35
- "string"
36
- ]
37
- },
38
29
  {
39
30
  "name": "errors",
40
31
  "type": "string[] | undefined",
@@ -69,11 +60,6 @@
69
60
  "type": "boolean",
70
61
  "isMethod": false
71
62
  },
72
- {
73
- "name": "noHtmlErrorText",
74
- "type": "string | undefined",
75
- "isMethod": false
76
- },
77
63
  {
78
64
  "name": "errors",
79
65
  "type": "string[] | undefined",
@@ -0,0 +1,159 @@
1
+ ---
2
+ component: VJoyFormFieldSkeleton
3
+ status: draft
4
+ language: en
5
+ dsds: JoyFormFieldSkeleton.dsds.json
6
+ dsdsVersion: "0.15.2"
7
+ ---
8
+
9
+ # Form field skeleton
10
+
11
+ Use **`VJoyFormFieldSkeleton`** when a form or form-like layout is loading and you need placeholder bars that match real field height and border radius.
12
+
13
+ It is a non-interactive shimmer block (100% width by default). Pair multiple instances inside **`VJoyWrapper`** to build skeleton forms; use `flex` and `width` to mirror horizontal rows or fixed-width fields.
14
+
15
+ | | |
16
+ | --- | --- |
17
+ | **Code** | `VJoyFormFieldSkeleton` from `@maltjoy/core-vue` |
18
+ | **Design** | [Figma — Joy Core Design System](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
19
+ | **Storybook** | [VJoyFormFieldSkeleton docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-skeletons-vjoyformfieldskeleton--docs) |
20
+ | **DSDS (machine)** | [`JoyFormFieldSkeleton.dsds.json`](./JoyFormFieldSkeleton.dsds.json) |
21
+ | **Live API** | `joy_get_component_info("VJoyFormFieldSkeleton")` |
22
+
23
+ ---
24
+
25
+ ## When to use this component
26
+
27
+ Use a form field skeleton while form content is fetching — for example a settings panel, onboarding step, or filter drawer where inputs will appear once data arrives.
28
+
29
+ Stack several skeletons in a column to suggest labels and fields, or place them in a row with `flex` to mimic split inputs (e.g. first name / last name).
30
+
31
+ ### When not to use this component
32
+
33
+ | Need | Use instead |
34
+ | --- | --- |
35
+ | Generic blocks, avatars, or arbitrary dimensions | `VJoyBlockSkeleton` (circle variant, custom height/width) |
36
+ | Full-page or section-blocking load | `VJoyScreenLoader` or `VJoySpinner` |
37
+ | Actual form input (even disabled) | `VJoyInput`, `VJoySelect`, etc. — skeletons are not form controls |
38
+ | Custom shimmer divs / ad-hoc placeholder CSS | Still use `VJoyFormFieldSkeleton` + Joy tokens |
39
+
40
+ ---
41
+
42
+ ## How it works
43
+
44
+ ### Anatomy
45
+
46
+ | Part | Required? | What it is |
47
+ | --- | --- | --- |
48
+ | Root `div` | Yes | `.joy-form-skeleton` block with shimmer animation |
49
+ | Size modifier | Yes | `joy-form-skeleton--{size}` — sets height and border radius from form size tokens |
50
+ | Shimmer | Yes | Neutral gradient animation (`shine` keyframes) — do not override in product CSS |
51
+
52
+ There are no slots, events, or child parts. The root is marked `aria-hidden="true"` because it is decorative loading chrome.
53
+
54
+ ### Default values
55
+
56
+ - `size` defaults to **`medium`** — also accepts `small`, `xsmall`, `xxsmall` (same scale as form fields)
57
+ - `flex` defaults to **`"1"`** — flex-grow when siblings sit inside `VJoyWrapper`
58
+ - `width` has **no default** — omit for full width; set a CSS length (e.g. `200px`) for fixed-width placeholders
59
+
60
+ ### Layout with `VJoyWrapper`
61
+
62
+ The component injects `VJoyWrapperContext` to adjust flex basis:
63
+
64
+ - **Row layout** (default) — equal-width siblings when `flex="1"` and no `width`
65
+ - **Column layout** (`direction="column"`) — flex basis becomes `auto` so stacked skeletons keep natural field height
66
+ - **Proportional rows** — set different `flex` values (`2`, `3`, `1`) on siblings for unequal column widths
67
+
68
+ Props and exposed members: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
69
+
70
+ ---
71
+
72
+ ## Best practices
73
+
74
+ Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
75
+
76
+ ### Layout and fidelity
77
+
78
+ - **SHOULD** wrap skeletons in **`VJoyWrapper`** — spacing and flex behavior are designed for it.
79
+ - **SHOULD** match **`size`** to the form fields that will replace the skeletons.
80
+ - **SHOULD** use **`width`** for fields that won’t span full width (e.g. a short submit-button placeholder).
81
+ - **SHOULD** use **`flex`** on siblings to mirror real horizontal field proportions.
82
+ - **MUST NOT** present skeletons as interactive controls or bind them to `v-model`.
83
+
84
+ ### Implementation
85
+
86
+ - **MUST** use only documented props (`size`, `width`, `flex`) from MCP or the component source.
87
+ - **MUST** use Joy tokens — no custom `.joy-form-skeleton` color or animation overrides.
88
+ - **MUST NOT** invent props (`variant`, `height`, `loading`, …).
89
+ - **MUST NOT** remove `aria-hidden` or add roles that imply a real input.
90
+
91
+ ### Do / don’t
92
+
93
+ | Do | Don’t |
94
+ | --- | --- |
95
+ | `VJoyWrapper` + stacked skeletons for a form | Lone skeletons with hand-rolled margins |
96
+ | `size="medium"` when fields will be medium | Random pixel heights in product CSS |
97
+ | `flex="2"` / `flex="3"` for split rows | Custom flex divs with neutral backgrounds |
98
+ | Swap skeletons for real fields when data loads | Disabled `VJoyInput` as a loading placeholder |
99
+
100
+ ---
101
+
102
+ ## Accessibility
103
+
104
+ Behaviors (what must happen), not slogans.
105
+
106
+ | Behavior | Expectation |
107
+ | --- | --- |
108
+ | Role | Decorative only — root has `aria-hidden="true"` |
109
+ | Announcement | Screen readers should **not** treat skeletons as form fields; ensure real labels/inputs appear when loading completes |
110
+ | Motion | Continuous shimmer animation — respect `prefers-reduced-motion` at the app level if product policy requires it |
111
+ | Focus | No focusable elements — don’t add `tabindex` or click handlers |
112
+
113
+ When loading finishes, replace skeletons with labelled form controls so users get proper names and validation feedback.
114
+
115
+ ---
116
+
117
+ ## Examples
118
+
119
+ ```vue
120
+ <script setup lang="ts">
121
+ import { VJoyFormFieldSkeleton, VJoyWrapper } from '@maltjoy/core-vue'
122
+ </script>
123
+
124
+ <template>
125
+ <!-- Stacked form fields -->
126
+ <VJoyWrapper direction="column" align="flex-end">
127
+ <VJoyFormFieldSkeleton />
128
+ <VJoyFormFieldSkeleton />
129
+ <VJoyFormFieldSkeleton />
130
+ <VJoyFormFieldSkeleton width="200px" />
131
+ </VJoyWrapper>
132
+
133
+ <!-- Horizontal row with proportional widths -->
134
+ <VJoyWrapper>
135
+ <VJoyFormFieldSkeleton flex="2" />
136
+ <VJoyFormFieldSkeleton flex="3" />
137
+ <VJoyFormFieldSkeleton flex="1" />
138
+ </VJoyWrapper>
139
+
140
+ <!-- Compact density -->
141
+ <VJoyWrapper>
142
+ <VJoyFormFieldSkeleton size="xsmall" width="200px" />
143
+ </VJoyWrapper>
144
+ </template>
145
+ ```
146
+
147
+ In Nuxt (or apps with auto-import), omit the import if `VJoyFormFieldSkeleton` is already global.
148
+
149
+ ---
150
+
151
+ ## Related
152
+
153
+ | Component | Use when |
154
+ | --- | --- |
155
+ | `VJoyWrapper` | Spacing and flex layout for skeleton rows and columns |
156
+ | `VJoyBlockSkeleton` | Non-form placeholders (text blocks, avatars, custom dimensions) |
157
+ | `VJoyInput` / `VJoySelect` / `VJoyTextarea` | Real form fields once data is ready |
158
+ | `VJoyFormGroup` | Label + field + error grouping after loading |
159
+ | `VJoySpinner` / `VJoyScreenLoader` | Inline or full-viewport loading indicators |
@@ -0,0 +1,285 @@
1
+ ---
2
+ component: VJoyFormGroup
3
+ status: draft
4
+ language: en
5
+ dsds: JoyFormGroup.dsds.json
6
+ dsdsVersion: "0.15.2"
7
+ ---
8
+
9
+ # Form group
10
+
11
+ Use **`VJoyFormGroup`** when a single form field needs a vertical stack — label, control, and validation message — with consistent inner spacing and predictable gaps between fields.
12
+
13
+ It is a layout primitive: one default slot, no props. It applies Joy spacing tokens, resets bottom margins on common Joy form children, and adds outer spacing when several groups are stacked.
14
+
15
+ | | |
16
+ | --- | --- |
17
+ | **Code** | `VJoyFormGroup` from `@maltjoy/core-vue` |
18
+ | **Design** | [Figma — Joy Core Design System](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
19
+ | **Storybook** | [VJoyFormGroup docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-functional-vjoyformgroup--docs) |
20
+ | **DSDS (machine)** | [`JoyFormGroup.dsds.json`](./JoyFormGroup.dsds.json) |
21
+ | **Live API** | `joy_get_component_info("VJoyFormGroup")` |
22
+
23
+ ---
24
+
25
+ ## When to use this component
26
+
27
+ Use a form group when one logical field is composed of several Joy pieces that should read as a unit — for example `VJoyLabel` + `VJoyInput` + `VJoyFormError`, or `VJoyLabel` + `VJoyCheckbox` + `VJoyFormError`.
28
+
29
+ Stack multiple `VJoyFormGroup` instances to build a form: each group is one field row; adjacent groups get automatic vertical separation via `--form-group-outer-gap`.
30
+
31
+ The component also normalizes spacing by clearing default bottom margins on supported Joy form children inside the group, so you don’t fight double margins between label, control, and error.
32
+
33
+ ### When not to use this component
34
+
35
+ | Need | Use instead |
36
+ | --- | --- |
37
+ | General flex layout (rows, button groups, arbitrary gap/direction) | `VJoyWrapper` |
38
+ | A set of mutually exclusive radio options | `VJoyRadioGroup` (fieldset + legend + layout) — **do not** wrap bare `VJoyRadio` in `VJoyFormGroup` |
39
+ | Multi-checkbox array with group legend | `VJoyCheckboxGroup` |
40
+ | Horizontal toolbars or filter rows | `VJoyWrapper` or `VJoyFilterBar` |
41
+ | Semantic grouping of related radios/checkboxes (fieldset) | `VJoyRadioGroup` / `VJoyCheckboxGroup` — not `VJoyFormGroup` alone |
42
+ | Custom `<div>` + manual margin CSS for the same job | Still use `VJoyFormGroup` + Joy tokens |
43
+
44
+ > **ESLint:** `@maltjoy/eslint-rules/no-vjoyformgroup-vjoyradio` flags `VJoyRadio` nested directly in `VJoyFormGroup`. Wrap radios in `VJoyRadioGroup` first; a `VJoyRadioGroup` inside `VJoyFormGroup` is valid.
45
+
46
+ ---
47
+
48
+ ## How it works
49
+
50
+ ### Anatomy
51
+
52
+ | Part | Required? | What it is |
53
+ | --- | --- | --- |
54
+ | Root container | Yes | `<div class="joy-form-group">`; receives `$attrs` (`id`, `data-testid`, `aria-*`, …) |
55
+ | Default slot | Yes | Label, form control(s), and optional `VJoyFormError` (or related helpers) |
56
+ | Inner gap | Yes | Column flex with `gap: var(--form-group-inner-gap)` (`--joy-core-spacing-2`) |
57
+ | Outer gap | Yes* | `margin-top` on a following `.joy-form-group` (`--form-group-outer-gap` → `--joy-core-spacing-8`) |
58
+
59
+ \*Applied only between consecutive form groups, not before the first.
60
+
61
+ ### Default values
62
+
63
+ `VJoyFormGroup` exposes **no props** and **no events**.
64
+
65
+ - Inner gap token: **`--joy-core-spacing-2`** (via `--form-group-inner-gap`)
66
+ - Outer gap between stacked groups: **`--joy-core-spacing-8`** (via `--form-group-outer-gap`)
67
+ - Margin reset (`margin-bottom: 0`) applies to: `.joy-label__group--standalone`, `.joy-checkbox`, `.joy-form-error`, `.joy-input--wrapper`, `.joy-radio-group-container`
68
+
69
+ Slots and attrs: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
70
+
71
+ ### Composition patterns
72
+
73
+ **External label** — `VJoyLabel` above the control; common for checkboxes and selects:
74
+
75
+ ```vue
76
+ <VJoyFormGroup>
77
+ <VJoyLabel text="Terms and conditions" />
78
+ <VJoyCheckbox v-model:checked="agreed" label="I accept the terms and conditions" />
79
+ <VJoyFormError v-if="showError" :errors="['You must accept the terms and conditions']" />
80
+ </VJoyFormGroup>
81
+ ```
82
+
83
+ **Built-in label** — control carries its own `label` prop (e.g. `VJoyInput`):
84
+
85
+ ```vue
86
+ <VJoyFormGroup>
87
+ <VJoyInput v-model="email" label="Email" name="email" :invalid="showError" />
88
+ <VJoyFormError v-if="showError" :errors="['You must enter an email']" />
89
+ </VJoyFormGroup>
90
+ ```
91
+
92
+ **Radio options** — use `VJoyRadioGroup` inside the group for the option set; add `VJoyFormError` when validation fails at group level.
93
+
94
+ ---
95
+
96
+ ## Best practices
97
+
98
+ Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
99
+
100
+ ### Structure
101
+
102
+ - **SHOULD** use one `VJoyFormGroup` per logical field (one label + one primary control + its errors).
103
+ - **SHOULD** order slot content top-to-bottom: label → control → error/helper.
104
+ - **SHOULD** stack fields by placing sibling `VJoyFormGroup` elements — don’t insert arbitrary wrappers that break outer-gap spacing.
105
+ - **MUST NOT** wrap a bare `VJoyRadio` in `VJoyFormGroup` — use `VJoyRadioGroup` (ESLint enforced).
106
+
107
+ ### Implementation
108
+
109
+ - **MUST** compose with Joy form components (`VJoyLabel`, `VJoyInput`, `VJoyFormError`, …) — don’t rely on raw `<input>` margins.
110
+ - **MUST** pass layout/test hooks via attrs on `VJoyFormGroup` (`id`, `data-testid`) when needed — there are no dedicated props.
111
+ - **MUST NOT** invent props on `VJoyFormGroup` — the API is slot-only.
112
+ - **MUST NOT** override `.joy-form-group` gap or adjacent `margin-top` with hardcoded pixel spacing in product CSS.
113
+ - **MUST NOT** re-add large `margin-bottom` on children inside the group — the component clears them on purpose.
114
+ - **SHOULD** pair `invalid` on the control with `VJoyFormError` text — color alone is not enough.
115
+
116
+ ### Do / don’t
117
+
118
+ | Do | Don’t |
119
+ | --- | --- |
120
+ | One group per field | One group wrapping an entire multi-field form without inner groups |
121
+ | `VJoyRadioGroup` for radio sets | Lone `VJoyRadio` inside `VJoyFormGroup` |
122
+ | Sibling `VJoyFormGroup` for vertical forms | Manual `margin-bottom: 24px` on every input |
123
+ | `VJoyWrapper` for non-form flex layouts | `VJoyFormGroup` for button rows or generic grids |
124
+ | Attrs on the root for `id` / `data-testid` | Fake props like `gap` or `spacing` |
125
+
126
+ ---
127
+
128
+ ## Accessibility
129
+
130
+ `VJoyFormGroup` is a **presentational** wrapper. It does not replace fieldset/legend semantics — use `VJoyRadioGroup` or `VJoyCheckboxGroup` when a group of options shares one legend.
131
+
132
+ | Behavior | Expectation |
133
+ | --- | --- |
134
+ | Role | Generic `<div>` — not a fieldset, not a label |
135
+ | Naming | Accessible names come from child controls and `VJoyLabel` / built-in labels |
136
+ | Grouping | For related options, use components that render `<fieldset>` + `<legend>` |
137
+ | Errors | `VJoyFormError` provides alert semantics; keep it inside the same form group as the invalid control |
138
+ | Attributes | `$attrs` merge onto the root — use `aria-labelledby` / `aria-describedby` only when wiring to real child `id`s |
139
+ | Focus | Focus moves to focusable children inside the slot; the wrapper itself is not focusable |
140
+
141
+ Target: **WCAG 2.2 AA** where the product requires it — meet requirements through child labels, errors, and proper option-group components.
142
+
143
+ ---
144
+
145
+ ## Examples
146
+
147
+ ```vue
148
+ <script setup lang="ts">
149
+ import { ref } from 'vue'
150
+ import {
151
+ VJoyFormGroup,
152
+ VJoyLabel,
153
+ VJoyInput,
154
+ VJoyCheckbox,
155
+ VJoySelect,
156
+ VJoyFormError,
157
+ } from '@maltjoy/core-vue'
158
+
159
+ const name = ref('')
160
+ const email = ref('')
161
+ const agreed = ref(false)
162
+ const plan = ref('')
163
+ const showErrors = ref(false)
164
+ </script>
165
+
166
+ <template>
167
+ <!-- Label + input + error -->
168
+ <VJoyFormGroup>
169
+ <VJoyLabel text="Full name" />
170
+ <VJoyInput
171
+ v-model="name"
172
+ name="signup-name"
173
+ placeholder="Enter your name"
174
+ :invalid="showErrors && !name"
175
+ />
176
+ <VJoyFormError v-if="showErrors && !name" :errors="['You must enter a name']" />
177
+ </VJoyFormGroup>
178
+
179
+ <!-- Built-in label on the control -->
180
+ <VJoyFormGroup>
181
+ <VJoyInput
182
+ v-model="email"
183
+ label="Email"
184
+ name="signup-email"
185
+ placeholder="name@company.com"
186
+ :invalid="showErrors && !email"
187
+ />
188
+ <VJoyFormError v-if="showErrors && !email" :errors="['You must enter an email']" />
189
+ </VJoyFormGroup>
190
+
191
+ <!-- Checkbox consent -->
192
+ <VJoyFormGroup>
193
+ <VJoyLabel text="Terms and conditions" />
194
+ <VJoyCheckbox
195
+ v-model:checked="agreed"
196
+ label="I accept the terms and conditions"
197
+ :invalid="showErrors && !agreed"
198
+ />
199
+ <VJoyFormError
200
+ v-if="showErrors && !agreed"
201
+ :errors="['You must accept the terms and conditions']"
202
+ />
203
+ </VJoyFormGroup>
204
+
205
+ <!-- Select without separate error in this example -->
206
+ <VJoyFormGroup>
207
+ <VJoyLabel text="Plan" />
208
+ <VJoySelect v-model="plan" name="signup-plan" :invalid="showErrors && !plan">
209
+ <option value="starter">Starter</option>
210
+ <option value="pro">Pro</option>
211
+ </VJoySelect>
212
+ </VJoyFormGroup>
213
+ </template>
214
+ ```
215
+
216
+ In Nuxt (or apps with auto-import), omit the import if `VJoyFormGroup` is already global.
217
+
218
+ ---
219
+
220
+ ## Related
221
+
222
+ | Component | Use when |
223
+ | --- | --- |
224
+ | `VJoyLabel` | Field title, required mark, legend text above a control |
225
+ | `VJoyFormError` | Validation message below the control (`role="alert"`) |
226
+ | `VJoyInput` / `VJoySelect` / `VJoyCheckbox` / … | The actual form control inside the group |
227
+ | `VJoyRadioGroup` | Exclusive radio options with fieldset semantics |
228
+ | `VJoyCheckboxGroup` | Multi-select checkboxes with group legend |
229
+ | `VJoyWrapper` | Non-field flex layouts, rows, or custom gap/direction |
230
+ | `VJoyInputPassword` | Password field with requirements slot (often stacked in a form group) |
231
+
232
+ ---
233
+
234
+ ## For agents
235
+
236
+ Optional for readers; **required** for vibe-coding quality. Maps to `agentDocumentBlocks` in [`JoyFormGroup.dsds.json`](./JoyFormGroup.dsds.json).
237
+
238
+ ### Pick the right component
239
+
240
+ | If the intent is… | Emit… | Do not emit… |
241
+ | --- | --- | --- |
242
+ | Label + one control + error stack | `VJoyFormGroup` wrapping children | A raw `<div class="field">` with manual margins |
243
+ | Vertical list of separate fields | Sibling `VJoyFormGroup` per field | One `VJoyFormGroup` around the whole form without per-field groups |
244
+ | Radio button set | `VJoyRadioGroup` (optionally inside `VJoyFormGroup`) | `VJoyFormGroup` > `VJoyRadio` without `VJoyRadioGroup` |
245
+ | Checkbox multi-select group | `VJoyCheckboxGroup` | `VJoyFormGroup` as fieldset substitute |
246
+ | Horizontal button/filter layout | `VJoyWrapper` | `VJoyFormGroup` |
247
+ | Arbitrary flex gap/direction | `VJoyWrapper` | Invented `gap` prop on `VJoyFormGroup` |
248
+
249
+ ### Hard rules
250
+
251
+ 1. **MUST** call `joy_get_component_info("VJoyFormGroup")` before generating markup — there are **no props**.
252
+ 2. **MUST** use the default slot only; pass `id`, `data-testid`, and `aria-*` via attributes on the component.
253
+ 3. **MUST NOT** nest `VJoyRadio` directly in `VJoyFormGroup` — wrap with `VJoyRadioGroup`.
254
+ 4. **MUST NOT** invent props (`gap`, `spacing`, `direction`, …).
255
+ 5. **MUST NOT** override `.joy-form-group` gap or stacked `margin-top` with custom CSS.
256
+ 6. **SHOULD** place `VJoyFormError` immediately after the invalid control in the same group.
257
+ 7. **SHOULD** use one `VJoyFormGroup` per logical field when building forms.
258
+
259
+ ### Checklist
260
+
261
+ - [ ] Correct component (`VJoyFormGroup` or named alternative)
262
+ - [ ] No invented props — slot + attrs only
263
+ - [ ] One group per field; siblings for multi-field forms
264
+ - [ ] Radios inside `VJoyRadioGroup`, not bare `VJoyRadio`
265
+ - [ ] `invalid` on control + `VJoyFormError` when validation fails
266
+ - [ ] No custom `.joy-form-group` spacing CSS
267
+ - [ ] Fieldset semantics delegated to `VJoyRadioGroup` / `VJoyCheckboxGroup` when needed
268
+
269
+ ---
270
+
271
+ ## DSDS mapping
272
+
273
+ This Markdown mirrors [`JoyFormGroup.dsds.json`](./JoyFormGroup.dsds.json) (DSDS **0.15.2**).
274
+
275
+ | Section here | DSDS block |
276
+ | --- | --- |
277
+ | When to use / not | `use-cases` |
278
+ | Anatomy | `anatomy` |
279
+ | Best practices | `guidelines` |
280
+ | Accessibility | `accessibility` |
281
+ | Examples | `sections` |
282
+ | For agents | `agentDocumentBlocks` |
283
+ | Slots / attrs | `api` in JSON + Joy MCP |
284
+ | Imports | `imports` in JSON |
285
+ | Token spacing | `design-specifications` (`--form-group-inner-gap`, `--form-group-outer-gap`) |