Split Preview

Editor and preview shown side by side.

Editor

Release notes: faster search and a refreshed dashboard.

Preview

Release notes: faster search and a refreshed dashboard.

html

<div class="spv spv--side-by-side" data-layout="side-by-side" style="--accent:#BFD9F2;">
  <div class="spv__pane" style="flex-basis:50%;">
    <p class="spv__label">Editor</p>
    <div class="spv__source" contenteditable="true" role="textbox" aria-multiline="true" aria-label="Source content">Release notes: faster search and a refreshed dashboard.</div>
  </div>
  <div class="spv__divider" aria-hidden="true">
    <span class="spv__sync" title="Scroll synced">&#8645;</span>
    
  </div>
  <div class="spv__pane spv__pane--preview">
    <p class="spv__label">Preview</p>
    <div class="spv__rendered">Release notes: faster search and a refreshed dashboard.</div>
  </div>
</div>
<style>
.spv { display: flex; font-family: system-ui, sans-serif; color: #241430; background: #FAF7F2; border: 1.5px solid #24143020; border-radius: 10px; overflow: hidden; max-width: 460px; }
.spv--stacked { flex-direction: column; }
.spv__pane { padding: 14px 16px; min-width: 0; }
.spv__pane--preview { flex: 1 1 0%; }
.spv--stacked .spv__pane--preview { flex: 1; }
.spv__label { margin: 0 0 6px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #241430aa; }
.spv__source { font-family: ui-monospace, monospace; font-size: 13px; white-space: pre-wrap; }
.spv__rendered { font-size: 15px; line-height: 1.6; }
.spv__divider { width: 1.5px; background: #24143020; display: flex; align-items: center; justify-content: center; position: relative; }
.spv--stacked .spv__divider { width: auto; height: 1.5px; }
.spv__sync { position: absolute; color: var(--accent); background: #FAF7F2; font-size: 13px; }
.spv__sync--off { color: #24143040; }
</style>

tailwind

<div class="spv-tw spv-tw--side-by-side flex max-w-[460px] bg-[#FAF7F2] text-[#241430] font-sans border-[1.5px] border-[#24143020] rounded-[10px] overflow-hidden" data-layout="side-by-side" style="--accent:#BFD9F2;">
  <div class="px-4 py-3.5 min-w-0" style="flex-basis:50%;">
    <p class="m-0 mb-1.5 text-[11px] font-bold uppercase tracking-wide text-[#241430]/65">Editor</p>
    <div class="font-mono text-[13px] whitespace-pre-wrap" contenteditable="true" role="textbox" aria-multiline="true" aria-label="Source content">Release notes: faster search and a refreshed dashboard.</div>
  </div>
  <div class="spv-tw__divider w-[1.5px] bg-[#24143020] relative flex items-center justify-center" aria-hidden="true">
    <span class="absolute text-[13px] bg-[#FAF7F2]" style="color:#BFD9F2" title="Scroll synced">&#8645;</span>
    
  </div>
  <div class="px-4 py-3.5 min-w-0 flex-1">
    <p class="m-0 mb-1.5 text-[11px] font-bold uppercase tracking-wide text-[#241430]/65">Preview</p>
    <div class="text-[15px] leading-relaxed">Release notes: faster search and a refreshed dashboard.</div>
  </div>
</div>
<style>
.spv-tw--stacked { flex-direction: column; }
.spv-tw--stacked .spv-tw__divider { width: auto; height: 1.5px; }
</style>

react

export function SplitPreview({
  editorContent = "Release notes: faster search and a refreshed dashboard.",
  layout = "side-by-side",
  accent = "#BFD9F2",
  syncScroll = true,
  splitRatio = 50,
}) {
  const stacked = layout === "stacked";
  return (
    <div style={{ display: "flex", flexDirection: stacked ? "column" : "row", fontFamily: "system-ui, sans-serif", color: "#241430", background: "#FAF7F2", border: "1.5px solid #24143020", borderRadius: 10, overflow: "hidden", maxWidth: 460 }}>
      <div style={{ padding: "14px 16px", minWidth: 0, flexBasis: stacked ? "auto" : `${splitRatio}%` }}>
        <p style={{ margin: "0 0 6px", fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".04em", color: "#241430aa" }}>Editor</p>
        <div contentEditable suppressContentEditableWarning role="textbox" aria-multiline="true" aria-label="Source content" style={{ fontFamily: "ui-monospace, monospace", fontSize: 13, whiteSpace: "pre-wrap" }}>{editorContent}</div>
      </div>
      <div aria-hidden="true" style={{ width: stacked ? "auto" : 1.5, height: stacked ? 1.5 : "auto", background: "#24143020", display: "flex", alignItems: "center", justifyContent: "center", position: "relative" }}>
        <span title={syncScroll ? "Scroll synced" : "Scroll not synced"} style={{ position: "absolute", fontSize: 13, background: "#FAF7F2", color: syncScroll ? accent : "#24143040" }}>{"\u21C5"}</span>
      </div>
      <div style={{ padding: "14px 16px", minWidth: 0, flex: 1 }}>
        <p style={{ margin: "0 0 6px", fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".04em", color: "#241430aa" }}>Preview</p>
        <div style={{ fontSize: 15, lineHeight: 1.6 }}>{editorContent}</div>
      </div>
    </div>
  );
}