Skip to content

feat: label position (top, bottom, before, after) for fields - #525

Merged
kevinchappell merged 19 commits into
mainfrom
feat/243-label-position
Oct 1, 2026
Merged

kevinchappell merged 19 commits into
mainfrom
feat/243-label-position

Conversation

@kevinchappell

Copy link
Copy Markdown
Collaborator

Summary

  • New config.labelPosition: top, bottom, before or after. before/after put the label beside the control (mirrored in RTL) and stack when the column is narrow. DOM order always matches what you see.
  • The Configuration panel offers it as a Label Position dropdown. Config declarations can now have options, which makes the item a dropdown. Adding Label Position from "Add config" starts at the position the field already shows.
  • Rendered fields with a visible label get a stable wrapper class: f-field f-label-<position>.
  • Checkbox and radio groups are named by their label (role="group" + aria-labelledby). Before this, the group label's for pointed at a <div>.
  • New CSS custom property --formeo-label-width (10rem).
  • Translations for the new keys: Draggable/formeo-languages branch feat/label-position-keys. Until that ships, the panel falls back to "Label Position" and "Top"/"Bottom"/"Before"/"After".

Compatibility

  • config.labelAfter still renders the same (true is bottom, or after for a lone checkbox/radio). The editor converts it to labelPosition on load, so forms save with labelPosition.
  • Group labels lose for="f-<id>" and gain id="f-<id>-label". Update any selector like label[for="f-<groupId>"].
  • Stacked labels are display: block. A labelled field's wrapper had class=""; it now has f-field f-label-<position>.
  • In the editor, while a field is being edited, a bottom/after label shows above the edit window instead of below it.
  • Editor configs that disabled or relabelled labelAfter (for example disabled: ['config.labelAfter'] or panels.config.options.labelAfter) no longer have any effect. Switch those keys to labelPosition.

Test plan

  • npm test (890 passing), npm run test:types, npm run lint, npm run build:lib
  • Playwright: tests/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.json diff is only labelPosition/labelAfter

Fixes #243

Please squash-merge.

Copilot AI balanced review requested due to automatic review settings October 1, 2026 12:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Medium severity

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, and after label positions with legacy labelAfter migration.
  • 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,

@kevinchappell kevinchappell Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@kevinchappell
kevinchappell merged commit 30606cd into main Oct 1, 2026
2 checks passed
@kevinchappell
kevinchappell deleted the feat/243-label-position branch October 1, 2026 13:25
github-actions Bot pushed a commit that referenced this pull request Oct 1, 2026
# [5.16.0](v5.15.1...v5.16.0) (2026-10-01)

### Features

* label position (top, bottom, before, after) for fields ([#525](#525)) ([30606cd](30606cd)), closes [#243](#243)
@kevinchappell

Copy link
Copy Markdown
Collaborator Author

🎉 This PR is included in version 5.16.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to custom the position of the label of each control

2 participants