feat: label position (top, bottom, before, after) for fields - #525
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The generic configuration API mishandles non-field labelPosition keys and its TypeScript contract permits declarations rejected at runtime.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Adds configurable field-label positioning across the editor, renderer, styling, schema, types, accessibility, and documentation.
Changes:
- Supports
top,bottom,before, andafterlabel positions with legacylabelAftermigration. - Adds dropdown-based configuration declarations and responsive/RTL styling.
- Improves checkbox/radio group labelling and expands automated coverage.
| File | Description |
|---|---|
tools/formdata-schema.mjs |
Adds label-position schema fields. |
tests/label-position.spec.js |
Adds end-to-end position and accessibility tests. |
tests/add-configuration.spec.js |
Updates configuration-panel tests. |
src/types/formeo.test-d.ts |
Tests new public types. |
src/types/formeo.d.ts |
Defines label positions and dropdown declarations. |
src/lib/sass/css-properties.test.mjs |
Tests label-width and layout CSS. |
src/lib/sass/components/_group-actions.scss |
Updates hover styling for wrappers. |
src/lib/sass/base/_properties.scss |
Adds the label-width custom property. |
src/lib/sass/base/_bs.scss |
Implements responsive label layouts. |
src/lib/sass/__fixtures__/formeo-baseline.css |
Updates compiled CSS fixture. |
src/lib/js/renderer/option-groups.test.js |
Updates group-label assertion. |
src/lib/js/renderer/label-position.test.js |
Tests renderer positioning and groups. |
src/lib/js/renderer/index.js |
Adds wrapper position classes. |
src/lib/js/formdata-schema.test.mjs |
Tests schema validation. |
src/lib/js/components/fields/index.js |
Declares the position dropdown. |
src/lib/js/components/fields/field.js |
Normalizes and renders editor labels. |
src/lib/js/components/fields/field-label-position.test.js |
Tests editor migration and wrappers. |
src/lib/js/components/edit-panel/edit-panel.test.js |
Tests dropdown declarations and defaults. |
src/lib/js/components/edit-panel/edit-panel.js |
Initializes added label positions. |
src/lib/js/components/edit-panel/edit-panel-item.mjs |
Renders declared options as selects. |
src/lib/js/components/edit-panel/config-options.test.mjs |
Tests dropdown declaration validation. |
src/lib/js/components/edit-panel/config-options.mjs |
Validates and labels dropdown choices. |
src/lib/js/components/controls/control-config-options.test.js |
Updates default field options. |
src/lib/js/common/label-position.test.mjs |
Tests position resolution and migration. |
src/lib/js/common/label-position.mjs |
Centralizes label-position logic. |
src/lib/js/common/dom.test.js |
Tests label DOM ordering. |
src/lib/js/common/dom.js |
Adds group ARIA naming and ordering. |
README.md |
Documents label-width theming. |
docs/renderer/renderer.md |
Documents renderer behavior and compatibility. |
docs/options/controls/README.md |
Documents control dropdown declarations. |
docs/options/config/README.md |
Documents configuration options. |
docs/css-frameworks.md |
Documents field wrapper classes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const newConfig = new EditPanelItem({ | ||
| key: `config.${configKey}`, | ||
| data: declaration.default, | ||
| data: configKey === 'labelPosition' ? resolveLabelPosition(this.component.data) : declaration.default, |
There was a problem hiding this comment.
Good catch, fixed in d908c13. The resolved-position start now applies only when the component is a field. A stage, row or column that declares its own labelPosition option keeps its declared default. Covered by a new test in edit-panel.test.js.
… position A stage, row or column that declares its own labelPosition config option keeps its declared default when it's added from the dialog. Those components have no field label to resolve. Refs #243
|
🎉 This PR is included in version 5.16.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |

Summary
config.labelPosition:top,bottom,beforeorafter.before/afterput the label beside the control (mirrored in RTL) and stack when the column is narrow. DOM order always matches what you see.options, which makes the item a dropdown. Adding Label Position from "Add config" starts at the position the field already shows.f-field f-label-<position>.role="group"+aria-labelledby). Before this, the group label'sforpointed at a<div>.--formeo-label-width(10rem).feat/label-position-keys. Until that ships, the panel falls back to "Label Position" and "Top"/"Bottom"/"Before"/"After".Compatibility
config.labelAfterstill renders the same (trueisbottom, orafterfor a lone checkbox/radio). The editor converts it tolabelPositionon load, so forms save withlabelPosition.for="f-<id>"and gainid="f-<id>-label". Update any selector likelabel[for="f-<groupId>"].display: block. A labelled field's wrapper hadclass=""; it now hasf-field f-label-<position>.bottom/afterlabel shows above the edit window instead of below it.labelAfter(for exampledisabled: ['config.labelAfter']orpanels.config.options.labelAfter) no longer have any effect. Switch those keys tolabelPosition.Test plan
npm test(890 passing),npm run test:types,npm run lint,npm run build:libtests/label-position.spec.js(each position in editor and renderer, narrow, RTL, long label, condition-hidden before/after fields, label click focus, group names) plus the full suite (206 passed, 3 skipped)dist/formData_schema.jsondiff is onlylabelPosition/labelAfterFixes #243
Please squash-merge.