Skip to content

Update the Slate HTML serializer to stop escaping text nodes so </> stay as-is#3211

Merged
ddouglasz merged 15 commits intomainfrom
CRAFT-2040-rich-text-input-destroys-hyperlink-tag
Feb 23, 2026
Merged

Update the Slate HTML serializer to stop escaping text nodes so </> stay as-is#3211
ddouglasz merged 15 commits intomainfrom
CRAFT-2040-rich-text-input-destroys-hyperlink-tag

Conversation

@ddouglasz
Copy link
Contributor

Summary

Update the Slate HTML serializer to stop escaping text nodes so </> stay as-is when the editor value is serialized, which allows tags to pass through.

@ddouglasz ddouglasz requested a review from a team as a code owner January 19, 2026 11:50
@changeset-bot
Copy link

changeset-bot bot commented Jan 19, 2026

🦋 Changeset detected

Latest commit: 73d2cb1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 98 packages
Name Type
@commercetools-uikit/rich-text-utils Minor
@commercetools-uikit/localized-rich-text-input Minor
@commercetools-uikit/rich-text-input Minor
@commercetools-uikit/inputs Minor
@commercetools-frontend/ui-kit Minor
@commercetools-uikit/design-system Minor
@commercetools-uikit/calendar-time-utils Minor
@commercetools-uikit/calendar-utils Minor
@commercetools-uikit/hooks Minor
@commercetools-uikit/i18n Minor
@commercetools-uikit/localized-utils Minor
@commercetools-uikit/utils Minor
@commercetools-uikit/accessible-hidden Minor
@commercetools-uikit/avatar Minor
@commercetools-uikit/card Minor
@commercetools-uikit/collapsible-motion Minor
@commercetools-uikit/collapsible-panel Minor
@commercetools-uikit/collapsible Minor
@commercetools-uikit/constraints Minor
@commercetools-uikit/data-table-manager Minor
@commercetools-uikit/data-table Minor
@commercetools-uikit/field-errors Minor
@commercetools-uikit/field-label Minor
@commercetools-uikit/field-warnings Minor
@commercetools-uikit/filters Minor
@commercetools-uikit/grid Minor
@commercetools-uikit/icons Minor
@commercetools-uikit/label Minor
@commercetools-uikit/link Minor
@commercetools-uikit/loading-spinner Minor
@commercetools-uikit/messages Minor
@commercetools-uikit/notifications Minor
@commercetools-uikit/pagination Minor
@commercetools-uikit/primary-action-dropdown Minor
@commercetools-uikit/progress-bar Minor
@commercetools-uikit/quick-filters Minor
@commercetools-uikit/stamp Minor
@commercetools-uikit/tag Minor
@commercetools-uikit/text Minor
@commercetools-uikit/tooltip Minor
@commercetools-uikit/view-switcher Minor
@commercetools-uikit/accessible-button Minor
@commercetools-uikit/flat-button Minor
@commercetools-uikit/icon-button Minor
@commercetools-uikit/link-button Minor
@commercetools-uikit/primary-button Minor
@commercetools-uikit/secondary-button Minor
@commercetools-uikit/secondary-icon-button Minor
@commercetools-uikit/dropdown-menu Minor
@commercetools-uikit/async-creatable-select-field Minor
@commercetools-uikit/async-select-field Minor
@commercetools-uikit/creatable-select-field Minor
@commercetools-uikit/date-field Minor
@commercetools-uikit/date-range-field Minor
@commercetools-uikit/date-time-field Minor
@commercetools-uikit/localized-multiline-text-field Minor
@commercetools-uikit/localized-text-field Minor
@commercetools-uikit/money-field Minor
@commercetools-uikit/multiline-text-field Minor
@commercetools-uikit/number-field Minor
@commercetools-uikit/password-field Minor
@commercetools-uikit/radio-field Minor
@commercetools-uikit/search-select-field Minor
@commercetools-uikit/select-field Minor
@commercetools-uikit/text-field Minor
@commercetools-uikit/time-field Minor
@commercetools-uikit/async-creatable-select-input Minor
@commercetools-uikit/async-select-input Minor
@commercetools-uikit/checkbox-input Minor
@commercetools-uikit/creatable-select-input Minor
@commercetools-uikit/date-input Minor
@commercetools-uikit/date-range-input Minor
@commercetools-uikit/date-time-input Minor
@commercetools-uikit/input-utils Minor
@commercetools-uikit/localized-money-input Minor
@commercetools-uikit/localized-multiline-text-input Minor
@commercetools-uikit/localized-text-input Minor
@commercetools-uikit/money-input Minor
@commercetools-uikit/multiline-text-input Minor
@commercetools-uikit/number-input Minor
@commercetools-uikit/password-input Minor
@commercetools-uikit/radio-input Minor
@commercetools-uikit/search-select-input Minor
@commercetools-uikit/search-text-input Minor
@commercetools-uikit/select-input Minor
@commercetools-uikit/select-utils Minor
@commercetools-uikit/selectable-search-input Minor
@commercetools-uikit/text-input Minor
@commercetools-uikit/time-input Minor
@commercetools-uikit/toggle-input Minor
@commercetools-uikit/spacings-inline Minor
@commercetools-uikit/spacings-inset-squish Minor
@commercetools-uikit/spacings-inset Minor
@commercetools-uikit/spacings-stack Minor
@commercetools-uikit/buttons Minor
@commercetools-uikit/fields Minor
@commercetools-uikit/spacings Minor
visual-testing-app Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Jan 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ui-kit Ready Ready Preview, Comment Feb 23, 2026 6:11pm

Request Review

const serializeNode = (node: TNode): Html => {
if (Text.isText(node)) {
let string = escapeHtml(node.text);
let string = node.text;
Copy link
Contributor

@misama-ct misama-ct Jan 19, 2026

Choose a reason for hiding this comment

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

Not being familiar with the code and the context, but at first glance this change seems to open the flood-gates. It looks to me now as if a user could pass any html. Can you verify this is not the case?

Did you just check if a-tags pass now, or did you also check with malicious content (e.g. script-tags).

It might be a good idea to add tests for this 2 behaviors as well (allows tags, prevents malicious html).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh good catch! I was already going to discuss this with the team. After raising the PR, I am beginning to see that destroying the tags was a deliberate decision. Maybe we can select some allowed tags to be passed at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I handled it in this commit. To only allow these tags, any other tags will be destroyed.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think so too. Ideally on a case by case basis. Wether or not its worth it for ui-kit is another question though.

import { canUseDOM } from '@commercetools-uikit/utils';

type Html = string;
const ALLOWED_HTML_TAGS = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to have a conversation about this list here. So far, these are the use cases we want to take care of that I can think of, also these ones seem to serialise.

@ddouglasz ddouglasz requested a review from a team January 20, 2026 13:52
Copy link
Contributor

@ByronDWall ByronDWall left a comment

Choose a reason for hiding this comment

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

Outside of 'should we allow links at all', I have some questions about what the desired end state of this pr should be, is it:

  1. users can enter html tags as plaintext and they won't get destroyed, or
  2. users should be able to format text into these tags using the WYSIWYG controls, similar to how they can format headings, lists, etc?

I was out last week - is this in response to a customer request / issue?

Comment on lines +48 to +49
'tbody',
'table',
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a usecase for allowing table elements? Right now there is no way to visually format a table in the editor, and adding one seems way outside the scope of this PR.

If we are allowing plaintext table elements, should we whitelist all the necessary tags?
thead
tfoot
tr
th
td
colgroup
col
caption

const serializeNode = (node: TNode): Html => {
if (Text.isText(node)) {
let string = escapeHtml(node.text);
let string = node.text;
Copy link
Contributor

Choose a reason for hiding this comment

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

Assuming we are going to allow all these tags, do we also want to add controls to apply them? In this implementation, you have to manually type out the <a> tag and attributes, e.g. <a href="test.com">test</a>.

From what I can tell, when you input an a tag, the editor doesn't display the a tag as a link, but as the full html text - is this the intended result?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ByronDWall per our last meeting, I whitelisted the anchor tag and covered some common security concerns in how its used on this commit 471cfd0

Thank you.

@ddouglasz ddouglasz force-pushed the CRAFT-2040-rich-text-input-destroys-hyperlink-tag branch from 5478e6c to 46f507d Compare February 5, 2026 11:48
@ddouglasz ddouglasz requested a review from misama-ct February 9, 2026 08:49
@tylermorrisford
Copy link
Contributor

[preview_deployment]

@ct-changesets
Copy link
Contributor

ct-changesets bot commented Feb 19, 2026

Release workflow succeeded ✅\nSee details: Workflow Run

@ddouglasz ddouglasz merged commit 3904605 into main Feb 23, 2026
9 checks passed
@ddouglasz ddouglasz deleted the CRAFT-2040-rich-text-input-destroys-hyperlink-tag branch February 23, 2026 18:23
@ct-changesets ct-changesets bot mentioned this pull request Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants