You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The dotCMS form system is global CSS, not per-component styling. It lives in core-web/apps/dotcms-ui/src/style.css (lines 35-95) and is documented in docs/frontend/STYLING_STANDARDS.md:
Every rule is descendant-scoped to .form. A form that omits class="form" silently loses the entire system — .field, label typography, hint and error styles all stop applying.
The new Angular Edit Content editor (core-web/libs/edit-content) never adopted it. Design reported it directly: "the edit content screen is not taking the classes from the .form > .field > label". dotCMS/dotcms-claude-plugins#33 added this rule to the automated code reviewer, so new code is now covered — this issue is the backfill of the existing editor.
Verified findings
#
Finding
Location
1
Root <form> carries class="p-fluid h-full" — no .form, so every global rule below is inert across the whole screen
Visible bug: 7 field templates already use p-field-hint / p-field-error, but those classes only exist under .form and nothing defines them locally → hints and validation messages render unstyled today; errors are not red
text, select, radio, checkbox, calendar, custom, relationship field templates
6
Checkbox/radio option rows hand-roll flex items-center gap-2 instead of the global .form-checkbox / .form-radio
Approach — fix the abstraction, not the 18 templates
Put .form on the root <form> and make the four dot-card-field components emit the canonical markup (.field, clean <label>, dotFieldRequired). All 18 field templates then inherit the fix without being touched individually.
dot-card-field is kept, not deleted — it provides two things the global system does not: the field-error-marker and :host ::ng-deep dot-card-field-footer:empty { display: none }.
Measured visual impact
html { font-size: 14px } (libs/dotcms-scss/angular/styles.scss:38, loaded via apps/dotcms-ui/project.json:75), and no typography scale override exists in libs/dotcms-scss/tailwind/theme.css. So 1rem = 14px:
Property
Today
After
Delta
label font-size
14px (inherited)
text-sm = 0.875rem = 12.25px
−1.75px
label font-weight
400
font-medium = 500
+100
gap label ↔ control
gap-2 = 7px
gap-1 = 3.5px
−3.5px
hint colour
default text colour
text-gray-500
fixed
error colour
default text colour
text-red-500
fixed (the bug)
content-type row/column layout
mb-5 gap-9 / gap-8
unchanged
0
.form { space-y-5 }
—
inert (<p-tabs> is the form's only direct child)
0
checkbox/radio rows
flex items-center gap-2
flex flex-row items-center gap-2
0 — identical
required asterisk
class written by hand
same class via the directive
0
Nothing reflows: no field changes width, no column moves, no tab re-lays-out. The global values win — no local override is added to preserve today's look. Today the editor's labels are larger and lighter than every other admin form (dot-tags, locales, users, push publish all already render at 12.25px/500); that inconsistency is the reported problem.
Two implementation risks to resolve
1 — .form .field > label is a DIRECT-CHILD selector. If the <label> stays inside <dot-card-field-label>, that component's host element sits between .field and the label and the rule does not apply. This must be handled explicitly (e.g. display: contents on the dot-card-field-label host, or making the label a real direct child of .field). A backfill that adds .form and .field but leaves the label nested does not fix the reported problem.
2 — do NOT use checkIsRequiredControl mode. That mode reads Validators.required off the FormGroup, but required BLOCK_EDITOR fields in this editor use blockEditorRequiredValidator() instead (dot-edit-content-form.component.ts:766-778, getFieldValidators), so the asterisk would disappear from a required Block Editor. The source of truth for "required" here is the content type definition (field.required), exposed as the isRequired getter in libs/edit-content/src/lib/fields/shared/base-wrapper-field.ts:57-70. Use the directive's bare mode — <label dotFieldRequired> rendered conditionally on isRequired.
Acceptance Criteria
The root <form> in dot-edit-content-form.component.html carries class="form", and any hand-rolled layout class the global system already covers is removed from it.
dot-card-field emits <div class="field"> instead of <div class="flex flex-col gap-2">, and still renders the field-error-marker and hides an empty dot-card-field-footer.
.form .field > label actually applies in the rendered DOM — the <label> is an effective direct child of .field (verify in the browser, not just in the template).
No <label> inside libs/edit-content carries a class attribute with typography or spacing utilities.
dot-card-field-label uses the dotFieldRequired directive in bare mode and no longer writes p-label-input-required by hand; same for dot-form-file-editor.component.html:47.
A required Block Editor field still shows its asterisk (the blockEditorRequiredValidator case).
Checkbox and radio option rows use .form-checkbox / .form-radio instead of hand-rolled flex items-center gap-2.
Field hints render at text-sm text-gray-500 and validation errors at text-sm text-red-500 in the running editor — they render unstyled today.
language-field and site-field labels are clean, and site-field's for points at its own control id instead of "language-field".
The component SCSS that reimplements form layout in dot-form-file-editor and dot-form-import-url is removed in favour of the global system.
No local CSS or Tailwind utility is added anywhere in libs/edit-content to preserve the previous label typography or field gap.
Affected specs are updated and pnpm nx test edit-content and pnpm nx lint edit-content pass.
The editor is exercised manually against a content type covering all field types (text, textarea, select, radio, checkbox, date/time, tags, block editor, wysiwyg, relationship, category, binary/file/image, key-value, json, custom field, host-folder, line divider) with no visual regression beyond the measured deltas above.
Priority
Medium
Additional Context
Scope — core-web/libs/edit-content only: the root form, the dot-card-field family, the field templates that need it, and the sub-components and dialogs inside that library (file editor, import URL, relationship search fields, sidebar workflow dialog). Out of scope: other portlets, the legacy JSP/Dojo editor, and the content type row/column grid (mb-5 grid gap-9 / flex flex-col gap-8) — that grid expresses the content type's own layout, which the global .form system cannot represent, and .form's rhythm never reaches it.
Description
The dotCMS form system is global CSS, not per-component styling. It lives in
core-web/apps/dotcms-ui/src/style.css(lines 35-95) and is documented indocs/frontend/STYLING_STANDARDS.md:Every rule is descendant-scoped to
.form. A form that omitsclass="form"silently loses the entire system —.field, label typography, hint and error styles all stop applying.The new Angular Edit Content editor (
core-web/libs/edit-content) never adopted it. Design reported it directly: "the edit content screen is not taking the classes from the.form > .field > label".dotCMS/dotcms-claude-plugins#33added this rule to the automated code reviewer, so new code is now covered — this issue is the backfill of the existing editor.Verified findings
<form>carriesclass="p-fluid h-full"— no.form, so every global rule below is inert across the whole screenlibs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.html:9<div class="field">appears once in the entire library, and it sits under a<div class="p-fluid">with no.formancestor → dead class.../dot-edit-content-sidebar/components/dot-edit-content-sidebar-workflow/dot-edit-content-sidebar-workflow.component.html:75.fieldas<div class="flex flex-col gap-2">inside a component, and puts the<label>inside another componentlibs/edit-content/src/lib/fields/dot-card-field/**(4 components, 18 consumers)p-label-input-requiredis hand-written — it is the private output of thedotFieldRequireddirective and must never be written by handdot-card-field-label.component.html:3,dot-form-file-editor.component.html:47p-field-hint/p-field-error, but those classes only exist under.formand nothing defines them locally → hints and validation messages render unstyled today; errors are not redflex items-center gap-2instead of the global.form-checkbox/.form-radiodot-edit-content-radio-field.component.html,dot-edit-content-checkbox-field.component.htmlclass="mb-2 inline-block");site-fieldalso pointsforat"language-field"— a copy-paste a11y bug.../dot-select-existing-content/components/language-field/language-field.component.html:1,.../site-field/site-field.component.html:1__form,__field,__error-slot).../dot-edit-content-file-field/components/dot-form-file-editor/,.../dot-form-import-url/Approach — fix the abstraction, not the 18 templates
Put
.formon the root<form>and make the fourdot-card-fieldcomponents emit the canonical markup (.field, clean<label>,dotFieldRequired). All 18 field templates then inherit the fix without being touched individually.dot-card-fieldis kept, not deleted — it provides two things the global system does not: thefield-error-markerand:host ::ng-deep dot-card-field-footer:empty { display: none }.Measured visual impact
html { font-size: 14px }(libs/dotcms-scss/angular/styles.scss:38, loaded viaapps/dotcms-ui/project.json:75), and no typography scale override exists inlibs/dotcms-scss/tailwind/theme.css. So1rem = 14px:text-sm= 0.875rem = 12.25pxfont-medium= 500gap-2= 7pxgap-1= 3.5pxtext-gray-500text-red-500mb-5 gap-9/gap-8.form { space-y-5 }<p-tabs>is the form's only direct child)flex items-center gap-2flex flex-row items-center gap-2Nothing reflows: no field changes width, no column moves, no tab re-lays-out. The global values win — no local override is added to preserve today's look. Today the editor's labels are larger and lighter than every other admin form (dot-tags, locales, users, push publish all already render at 12.25px/500); that inconsistency is the reported problem.
Two implementation risks to resolve
1 —
.form .field > labelis a DIRECT-CHILD selector. If the<label>stays inside<dot-card-field-label>, that component's host element sits between.fieldand the label and the rule does not apply. This must be handled explicitly (e.g.display: contentson thedot-card-field-labelhost, or making the label a real direct child of.field). A backfill that adds.formand.fieldbut leaves the label nested does not fix the reported problem.2 — do NOT use
checkIsRequiredControlmode. That mode readsValidators.requiredoff theFormGroup, but requiredBLOCK_EDITORfields in this editor useblockEditorRequiredValidator()instead (dot-edit-content-form.component.ts:766-778,getFieldValidators), so the asterisk would disappear from a required Block Editor. The source of truth for "required" here is the content type definition (field.required), exposed as theisRequiredgetter inlibs/edit-content/src/lib/fields/shared/base-wrapper-field.ts:57-70. Use the directive's bare mode —<label dotFieldRequired>rendered conditionally onisRequired.Acceptance Criteria
<form>indot-edit-content-form.component.htmlcarriesclass="form", and any hand-rolled layout class the global system already covers is removed from it.dot-card-fieldemits<div class="field">instead of<div class="flex flex-col gap-2">, and still renders thefield-error-markerand hides an emptydot-card-field-footer..form .field > labelactually applies in the rendered DOM — the<label>is an effective direct child of.field(verify in the browser, not just in the template).<label>insidelibs/edit-contentcarries aclassattribute with typography or spacing utilities.dot-card-field-labeluses thedotFieldRequireddirective in bare mode and no longer writesp-label-input-requiredby hand; same fordot-form-file-editor.component.html:47.blockEditorRequiredValidatorcase)..form-checkbox/.form-radioinstead of hand-rolledflex items-center gap-2.text-sm text-gray-500and validation errors attext-sm text-red-500in the running editor — they render unstyled today.language-fieldandsite-fieldlabels are clean, andsite-field'sforpoints at its own control id instead of"language-field".dot-form-file-editoranddot-form-import-urlis removed in favour of the global system.libs/edit-contentto preserve the previous label typography or field gap.pnpm nx test edit-contentandpnpm nx lint edit-contentpass.Priority
Medium
Additional Context
Scope —
core-web/libs/edit-contentonly: the root form, thedot-card-fieldfamily, the field templates that need it, and the sub-components and dialogs inside that library (file editor, import URL, relationship search fields, sidebar workflow dialog). Out of scope: other portlets, the legacy JSP/Dojo editor, and the content type row/column grid (mb-5 grid gap-9/flex flex-col gap-8) — that grid expresses the content type's own layout, which the global.formsystem cannot represent, and.form's rhythm never reaches it.Reference implementation —
core-web/libs/portlets/dot-tags/src/lib/dot-tags-create/dot-tags-create.component.html(zero component SCSS):Related
core-web/apps/dotcms-ui/src/style.css:35-95docs/frontend/STYLING_STANDARDS.md(form system table + rules)core-web/libs/ui/src/lib/dot-field-required/dot-field-required.directive.tsdotCMS/dotcms-claude-plugins#33