A plain-text source editor using the browser's contenteditable="plaintext-only" editing surface. It keeps text, caret, native undo and composition through updates. Use cem-textarea for submitted form values and cem-workspace for an IDE layout. Source text is never executed or inserted as HTML.
This shared code editor powers the cem-demo-element playground. Explore the components gallery for related controls.
Full examples and variation matrix · Property playground · Automated stories
Type, paste multiline source, select text and undo. Change selection-request to apply a new UTF-16 source range; selection-focus explicitly requests focus.
{slice @name=label | Source code}
{slice @name=ariaLabel | Source document}
{slice @name=value | Hello source}
{slice @name=slotLabel | }
{slice @name=class | }
{slice @name=describedby | editor-help}
{slice @name=selectionStart | 0}
{slice @name=selectionEnd | 0}
{slice @name=selectionRequest | }
{slice @name=fit | content}{slice @name=dir | ltr}{slice @name=selectionDirection | none}
{slice @name=readonly | false}{slice @name=disabled | false}{slice @name=hidden | false}{slice @name=selectionFocus | false}
{cem-field @label="label" @value={datadom.slices.label} @slice=label @slice-event=input @slice-value="$target.value"}
{cem-field @label="aria-label" @value={datadom.slices.ariaLabel} @slice=ariaLabel @slice-event=input @slice-value="$target.value"}
{cem-textarea @label="value" @value={datadom.slices.value} @slice=value @slice-event=input @slice-value="$target.value"}
{cem-field @label="label slot" @value={datadom.slices.slotLabel} @slice=slotLabel @slice-event=input @slice-value="$target.value"}
{cem-field @label="class" @value={datadom.slices.class} @slice=class @slice-event=input @slice-value="$target.value"}
{cem-field @label="describedby" @value={datadom.slices.describedby} @slice=describedby @slice-event=input @slice-value="$target.value"}
{cem-field @label="selection-start" @value={datadom.slices.selectionStart} @slice=selectionStart @slice-event=input @slice-value="$target.value"}
{cem-field @label="selection-end" @value={datadom.slices.selectionEnd} @slice=selectionEnd @slice-event=input @slice-value="$target.value"}
{cem-field @label="selection-request" @value={datadom.slices.selectionRequest} @slice=selectionRequest @slice-event=input @slice-value="$target.value"}
{cem-checkbox @label="readonly" @checked={if datadom.slices.readonly { true } else { null }} @slice=readonly @slice-event=change @slice-value="$target.checked"}
{cem-checkbox @label="disabled" @checked={if datadom.slices.disabled { true } else { null }} @slice=disabled @slice-event=change @slice-value="$target.checked"}
{cem-checkbox @label="hidden" @checked={if datadom.slices.hidden { true } else { null }} @slice=hidden @slice-event=change @slice-value="$target.checked"}
{cem-checkbox @label="selection-focus" @checked={if datadom.slices.selectionFocus { true } else { null }} @slice=selectionFocus @slice-event=change @slice-value="$target.checked"}
{fieldset | {legend | fit}
{cem-radio @label=content @name=fit @value=content @checked={if datadom.slices.fit == "content" { true } else { null }} @slice=fit @slice-event=change @slice-value="$target.value"}
{cem-radio @label=fill @name=fit @value=fill @checked={if datadom.slices.fit == "fill" { true } else { null }} @slice=fit @slice-event=change @slice-value="$target.value"}
}
{fieldset | {legend | dir}
{cem-radio @label=ltr @name=dir @value=ltr @checked={if datadom.slices.dir == "ltr" { true } else { null }} @slice=dir @slice-event=change @slice-value="$target.value"}
{cem-radio @label=rtl @name=dir @value=rtl @checked={if datadom.slices.dir == "rtl" { true } else { null }} @slice=dir @slice-event=change @slice-value="$target.value"}
}
{fieldset | {legend | selection-direction}
{cem-radio @label=none @name=selectionDirection @value=none @checked={if datadom.slices.selectionDirection == "none" { true } else { null }} @slice=selectionDirection @slice-event=change @slice-value="$target.value"}
{cem-radio @label=forward @name=selectionDirection @value=forward @checked={if datadom.slices.selectionDirection == "forward" { true } else { null }} @slice=selectionDirection @slice-event=change @slice-value="$target.value"}
{cem-radio @label=backward @name=selectionDirection @value=backward @checked={if datadom.slices.selectionDirection == "backward" { true } else { null }} @slice=selectionDirection @slice-event=change @slice-value="$target.value"}
}
{cem-field @label="Source content type" @value={datadom.slices.contentType ?? "text/cem-ml"} @slice=contentType @slice-event=input @slice-value="$target.value"}
{cem-field @label="Source identity" @value={datadom.slices.sourceId} @slice=sourceId @slice-event=input @slice-value="$target.value"}
{cem-radio @label="Color automatically" @name=highlight @value=auto @checked={if (datadom.slices.highlight ?? "auto") == "auto" { true } else { null }} @slice=highlight @slice-event=change @slice-value="$target.value"}
{cem-radio @label="Plain source" @name=highlight @value=off @checked={if datadom.slices.highlight == "off" { true } else { null }} @slice=highlight @slice-event=change @slice-value="$target.value"}
{div @style="block-size:55vb" |
{cem-source-editor @content-type={datadom.slices.contentType ?? "text/cem-ml"} @highlight={datadom.slices.highlight ?? "auto"} @source-id={datadom.slices.sourceId} @label={datadom.slices.label} @aria-label={datadom.slices.ariaLabel} @value={datadom.slices.value} @slice=value @slice-event=input @slice-value="$target.value" @fit={datadom.slices.fit} @dir={datadom.slices.dir} @class={datadom.slices.class} @describedby={datadom.slices.describedby}
@readonly={if datadom.slices.readonly { true } else { null }} @disabled={if datadom.slices.disabled { true } else { null }} @hidden={if datadom.slices.hidden { true } else { null }}
@selection-start={datadom.slices.selectionStart} @selection-end={datadom.slices.selectionEnd} @selection-direction={datadom.slices.selectionDirection} @selection-request={datadom.slices.selectionRequest} @selection-focus={if datadom.slices.selectionFocus { true } else { null }} |
{cem:if @test=datadom.slices.slotLabel | {strong @slot=label | {$datadom.slices.slotLabel}}}
}
}
{cem-module-url @slice=source @src="@epa-wg/cem-components/components/cem-source-editor/cem-source-editor.xhtml"}{cem:if @test=datadom.slices.source | {cem-demo-element @legend="Canonical source" @src={datadom.slices.source} @type=xml @demo=false}}