Frontend: global form structure drift — 29 forms without .form, 58 labels overriding global typography, 21 required markers not using dotFieldRequired
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Problem
The dotCMS form system is global CSS in core-web/apps/dotcms-ui/src/style.css, and every rule is descendant-scoped to .form:
.form { @apply w-full space-y-5; }
.form .field { @apply flex flex-col gap-1; }
.form .field > label { @apply text-sm font-medium; }
.form .form-checkbox,
.form .form-radio { @apply flex flex-row items-center gap-2; }
.form .form-checkbox label,
.form .form-radio label { @apply text-sm font-normal; }
.form .p-field-hint { @apply text-sm text-gray-500; }
.form .p-field-error { @apply text-sm text-red-500; }
.p-label-input-required::after { content: "*"; } /* added by the dotFieldRequired directive */
Two consequences that are being violated across the app today:
- A
<form>that omitsclass="form"loses the entire system —.fieldgrouping, label typography, hint and error styling all stop applying. Those forms are then styled ad hoc, per component. - Because
.form .field > labelalready sets size and weight, a label carrying its owntext-sm/font-semibold/text-gray-900fights the global rule. This is why forms drift visually field by field across portlets.
docs/frontend/STYLING_STANDARDS.md documents Tailwind-first, PrimeNG theming, and tags vs chips — but says nothing about forms, so neither reviewers nor the automated PR reviewer ever caught this.
Measured drift (core-web/libs + core-web/apps, *.html)
| Violation | Count |
|---|---|
<form> without class="form" (of 59 total forms) |
29 |
<label> carrying typography classes (text-sm, font-medium, font-semibold, text-gray-*, text-surface-*, text-color) |
58 |
Hand-written class="p-label-input-required" instead of the dotFieldRequired directive |
13 |
Hardcoded <span ...>*</span> asterisks instead of the directive's ::after |
8 |
Deprecated PrimeFlex p-field wrappers instead of .field |
21 |
For reference: .field is used 163 times and dotFieldRequired 70 times, so the convention is well established — the above is drift against it, not an unadopted proposal.
By area
| Area | form w/o .form |
label w/ typography | manual required class | PrimeFlex p-field |
Total |
|---|---|---|---|---|---|
libs/portlets/dot-experiments |
7 | 16 | 1 | 1 | 25 |
apps/dotcms-ui |
5 | 14 | 0 | 6 | 25 |
libs/edit-content |
3 | 1 | 2 | 12 | 18 |
libs/new-block-editor |
1 | 10 | 0 | 0 | 11 |
libs/ui |
1 | 3 | 4 | 0 | 8 |
libs/portlets/dot-roles |
0 | 6 | 0 | 0 | 6 |
libs/block-editor |
4 | 0 | 1 | 0 | 5 |
libs/portlets/dot-locales |
1 | 0 | 2 | 2 | 5 |
libs/portlets/dot-content-drive |
0 | 4 | 0 | 0 | 4 |
libs/portlets/edit-ema |
2 | 1 | 1 | 0 | 4 |
libs/portlets/dot-users |
1 | 1 | 2 | 0 | 4 |
Remaining (dotcms-scss, dot-rules, dot-plugins, dot-tags, dotcdn) |
5 | 2 | 0 | 0 | 7 |
Reference implementation
libs/portlets/dot-tags/src/lib/dot-tags-create/dot-tags-create.component.html — the whole form, with zero component SCSS:
<form class="form" [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="field">
<label for="tagName" dotFieldRequired>{{ 'tags.form.tag-name' | dm }}</label>
<input pInputText id="tagName" formControlName="name" class="w-full" />
</div>
<div class="field">
<label for="tagSite">{{ 'tags.form.site' | dm }}</label>
<dot-site id="tagSite" formControlName="siteId" />
</div>
</form>
Acceptance criteria
- AC1 — Document the rule.
docs/frontend/STYLING_STANDARDS.mdgains a Forms section stating:<form>must carryclass="form"; each label + control is grouped in<div class="field">; checkbox/radio rows use.form-checkbox/.form-radio; labels carry noclassattribute and are a direct child of.field; labels of required controls use thedotFieldRequireddirective (with[checkIsRequiredControl]when the validator is dynamic); hints and errors use.p-field-hint/.p-field-error; no component SCSS for form layout. - AC2 — Every
<form>carriesclass="form". The 29 offenders are migrated, and the hand-rolled layout classes on the form tag (space-y-*,flex flex-col gap-*,w-full) are removed in the same edit. - AC3 — Labels are clean. The 58 labels drop their typography classes. Non-typographic behavioral classes (e.g.
cursor-pointeron a checkbox label) may stay. Any label not already a direct child of.fieldis unwrapped. - AC4 — Required markers come from the directive. The 13 hand-written
p-label-input-requiredclasses and 8 hardcoded*spans are replaced bydotFieldRequiredon the label. No template referencesp-label-input-requireddirectly. - AC5 — PrimeFlex removed from forms. The 21
p-fieldwrappers become.field(or.form-checkbox/.form-radiowhere the row is horizontal). - AC6 — No visual regression. Each migrated form is verified in the UI: field spacing (
space-y-5), label size/weight, required asterisk, and hint/error text all render as before or better. Existing specs pass; specs asserting on removed classes are updated. - AC7 — Guard against re-drift.
<form>withoutclass="form"and labels with typography classes are caught automatically going forward (thedotcms-workflowsPR reviewer already enforces this as of v3.3.0 — see note below; an ESLint/@angular-eslinttemplate rule would make it hard-fail in CI).
Suggested sequencing
The areas are independent, so this can land as one PR per area rather than a single large one. Suggested order by ratio of impact to risk: libs/portlets/dot-roles → libs/portlets/dot-content-drive → libs/new-block-editor → libs/edit-content → apps/dotcms-ui → libs/portlets/dot-experiments. libs/dotcms-scss/jsp/styleguide.html is a static styleguide page, not an Angular form — confirm whether it is in scope at all.
Related
The automated frontend PR reviewer now enforces these rules: dotCMS/dotcms-claude-plugins#33 (dotcms-workflows v3.3.0) adds a "Form Structure — Global Classes" checklist to dotcms-pr-code-reviewer, so new violations are flagged at review time. This issue covers the existing drift and AC1's missing documentation.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with docs/frontend/STYLING_STANDARDS.md and the reference implementation in libs/portlets/dot-tags/src/lib/dot-tags-create/dot-tags-create.component.html. Review the listed areas and migrate forms, labels, required markers, and p-field wrappers to the documented conventions, beginning with dot-roles and dot-content-drive. Done means all acceptance criteria are met, visual checks pass, and existing specs are updated as needed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, tailwindcss
- Domain
- documentation, frontend
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 42/100