Phenomenon
This entry documents a recurring implementation strategy, not one vendor’s defect. Some teams avoid live <a> nodes inside contenteditable and instead store the URL on span[data-href] (or similar) while editing, then serialize to <a href> for output HTML, email, or static pages.
Mainstream editor packages default to <a> in the DOM while editing—see Lexical @lexical/link, Tiptap Link, and typical ProseMirror link mark toDOM—so choosing spans is an explicit trade-off.
When this pattern appears
- Need click-to-caret without navigation unless modified (e.g. Ctrl/Cmd+click).
- Want to delay semantic link markup until a sanitization / merge pass.
- CMS or design tools that already use
data-*for internal references.
Trade-offs (summary)
| Topic | Risk if only span in the editor |
|---|---|
| Accessibility | Native link semantics missing until export or ARIA decoration |
| Clipboard | May need custom copy / cut handlers to emit <a> |
| Security | Must validate URLs on the same rules as href |
Related scenario
- scenario-link-span-internal-representation — full write-up, external references, and links to Lexical/Tiptap/Slate defaults
References
- Tip: Link insertion and editing — optional span subsection
- GrapesJS #610 — discussion of difficult link UX in WYSIWYG builders