dotCMS / dotCMS/core

Block Editor MVP: configurable text color palette per Story Block field

Open
#37,235 0 comments 0 reactions 1 assignee View on GitHub

@rjvelazco is already working on this.

Since Aug 26, 2026.

Team : Scout Type : Task
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Description

MVP for text color in the new Block Editor. A content type admin configures an explicit color palette on a Story Block field; an author can then apply only those colors to text. Nothing is exposed when no palette is configured.

This is deliberately the narrow slice out of the 8/25/26 huddle (#feat-block-editor-upgrade). Site-level cascade, design tokens (primary/secondary/accent/surface), Tailwind palette overrides, and Style Editor / UVE integration are out of scope — see Out of scope.

Storage contract

A single styleOptions field variable on the Story Block field, holding JSON. Only colors is implemented in this MVP; spacing and fontFamily are reserved in the shape so later work extends the same variable instead of adding new keys.

{
  "colors": [
    { "label": "Brand Blue", "value": "#1E40AF" },
    { "label": "Accent",     "value": "#F59E0B" }
  ],
  "spacing": [],
  "fontFamily": []
}
Decision Value Rationale
Storage fieldVariables key styleOptions Existing precedent — allowedBlocks, contentTypes, customBlocks. No backend model, DB, or REST contract change.
Format JSON, category-keyed arrays of { label, value } Extensible to spacing / fontFamily without a second variable or a rename.
Variable key styleOptions Category-agnostic; survives adding non-color options. Stored contract — renaming later breaks existing configs.
Author UX Configured swatches only The palette is a constraint, not a suggestion. Guarantees stored colors stay a known set.
No config Color control hidden Zero blast radius on existing Story Block fields.
Hex validity Enforced at author-time by p-colorPicker in field settings The admin never types a hex value.
Why the backend needs no change

Field variables are already a generic key/value store persisted per field (DotFieldVariablesService → field variables REST endpoint). The requested "backend key/value pair saved as a field value" is exactly what fieldVariables provides. No Java, DB, or openapi.yaml change is expected. If implementation proves otherwise, flag it on this issue before writing backend code.

Implementation anchors
Area Location Note
Admin settings UI core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/dot-block-editor-settings/ Add a "Text colors" section with repeatable label + p-colorPicker rows.
⚠️ Save/load serializer dot-block-editor-settings.component.tssaveSettings() uses value?.join(','), ngOnInit uses value.split(',') Blocker: every setting is assumed to be a comma-joined array. A JSON value cannot pass through unchanged — settingsMap needs a per-setting serialize/deserialize hook.
JSON-from-field-variable precedent same file — getCustomBlockOptions() customBlocks already parses JSON out of a field variable with a try/catch + console.warn fallback. Mirror it.
Field variable read core-web/libs/new-block-editor/src/lib/editor/editor.component.ts:110-130 parseAllowedBlocks / parseAllowedContentTypes are the pattern for a parseStyleOptions.
Store wiring editor.component.ts constructor effects (~L592-602) Same shape as setAllowedBlocks / setAllowedContentTypes.
Author toolbar core-web/libs/new-block-editor/src/lib/editor/components/toolbar/ New color control + EditorToolbarStore mark-state mirror.
TipTap dependency core-web/package.json @tiptap/extension-text-style is not currently a dependency (TipTap 3.22.2). Needed for the TextStyle + Color marks.
Feature flag FeaturedFlags enum, core-web/libs/dotcms-models/src/lib/shared-models.ts:44 New flag, e.g. FEATURE_FLAG_BLOCK_EDITOR_STYLE_OPTIONS.

Note on TipTap node/mark names: per core-web/libs/new-block-editor/CLAUDE.md, mark and node names are an immutable stored contract. Use TipTap's upstream textStyle mark rather than inventing a dot-prefixed one, so the stored JSON stays portable.

Acceptance Criteria

Admin — configuring the palette

  • Story Block field settings shows a Text colors section with repeatable rows, each row a text label input plus a PrimeNG p-colorPicker.
  • Admin can add a row, remove a row, and reorder is not required for this MVP.
  • Saving serializes the rows to the styleOptions field variable as JSON with a colors array of { label, value }.
  • spacing and fontFamily keys are preserved on save when already present in the stored JSON — the color editor must not drop them.
  • Reopening field settings rehydrates the existing rows from the stored styleOptions JSON.
  • Removing every color row and saving clears/deletes the styleOptions variable rather than storing an empty-array JSON blob.
  • Existing allowedBlocks (comma-joined) save/load continues to work unchanged after the serializer refactor — covered by a spec.

Author — applying a color

  • With styleOptions.colors configured, the block editor toolbar shows a color control listing exactly the configured swatches, each labelled with its label.
  • Selecting a swatch applies the color to the current text selection and it renders in the editor.
  • The applied color persists in the stored TipTap JSON via the textStyle mark and survives a save → reload cycle.
  • A "remove color" / clear action returns the selection to inherited color.
  • The toolbar control reflects the active selection's color (mirrored through EditorToolbarStore).
  • Authors cannot enter an arbitrary hex — the control offers only the configured swatches.

No config / degraded input (sad path)

  • Field with no styleOptions variable → the color control is not rendered; no console error.
  • styleOptions present but colors is missing, empty, or not an array → control hidden, console.warn, editor loads normally.
  • Malformed JSON in styleOptions → caught, console.warn, editor loads normally, no other field variable is affected.
  • Entry with a missing/blank label → falls back to its hex value as the label rather than rendering a blank swatch.
  • Entry with a missing or non-hex value → skipped; remaining valid entries still render.
  • Duplicate hex values with different labels → both render; no dedupe required.

Feature flag & rollout

  • Whole feature sits behind a new feature flag; flag off → neither the admin section nor the toolbar control appears.
  • Flag off with styleOptions already stored → stored colors still render in existing content (read path is not gated), only authoring is.

Tests

  • Unit specs in core-web/libs/new-block-editor/ for parseStyleOptions covering valid, empty, malformed, and partially-invalid input.
  • Unit specs for the admin settings serializer covering JSON round-trip and the allowedBlocks regression.
  • Unit spec for the toolbar color control: swatch list rendering, apply, clear, active-state mirroring.

Priority

Medium

Additional Context

Out of scope

Explicitly not in this MVP — each needs its own issue:

  • Site-level color configuration that cascades to blocks and block editor fields.
  • Semantic design tokens (primary / secondary / accent / surface) as a layer over raw hex.
  • Tailwind palette structure and default-color overrides.
  • Style Editor / UVE integration (registerStyleEditorSchema).
  • spacing and fontFamily — shape is reserved, behavior is not implemented.
  • Background color, and the legacy libs/block-editor (rollback path only).
Huddle context

From the 8/25/26 huddle in #feat-block-editor-upgrade (Rafael Velazco, Freddy Montes, Adrian Molina):

  • Freddy proposed design tokens as a semantic layer over raw color values, with site-level configuration cascading down to blocks and fields.
  • Rafael raised concern about the blast radius of styling changes across the system — this MVP answers that by scoping config to a single field and hiding the control when unset.
  • Freddy proposed starting with a simple color input in the style editor and block editor, and noted spacing and font family could extend the same mechanism later.
  • Feature-flag gating was called out in the huddle as the control for whether color appears in the block editor.
  • Roadmap was flagged as tight, which is the reason this ticket is a single field-scoped slice rather than the full token system.

Huddle notes were AI-generated and may contain inaccuracies.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.