UI Components Hard
Inline Code Link Popover
Apply inline code or a link to a text selection using a small selection popover.
Open in Lab
MCP
html css javascript react
Targets: TS JS HTML React
Code
:root {
--bg: #0b1020;
--panel: #121a2d;
--panel-2: #101a30;
--line: #263555;
--text: #eef2ff;
--muted: #9eb1d4;
--accent: #78a9ff;
--code: #ffb4a2;
color-scheme: dark;
}
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: radial-gradient(900px 500px at 20% -10%, #182034 0%, var(--bg) 60%);
color: var(--text);
font: 15px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif;
padding: clamp(1rem, 4vw, 3rem);
}
button, input { font: inherit; }
.demo {
width: min(720px, 100%);
margin: auto;
}
.head h1 { margin: 0 0 8px; font-size: 22px; letter-spacing: -0.01em; }
.head p { margin: 0 0 24px; color: var(--muted); font-size: 13.5px; max-width: 62ch; }
kbd {
font: 11px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
background: var(--panel-2);
border: 1px solid var(--line);
border-bottom-width: 2px;
border-radius: 5px;
padding: 3px 5px;
}
.editor {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 14px;
padding: 22px 24px;
min-height: 170px;
outline: none;
box-shadow: 0 20px 60px -30px #000;
transition: border-color .18s ease;
}
.editor:focus-visible { border-color: var(--accent); }
.editor p { margin: 0 0 14px; }
.editor p:last-child { margin-bottom: 0; }
.editor ::selection { background: #78a9ff55; }
.editor code {
font: 0.88em/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
background: #2b1f24;
color: var(--code);
border: 1px solid #45313a;
border-radius: 5px;
padding: 1px 5px;
}
.editor a {
color: var(--accent);
text-decoration: underline;
text-underline-offset: 3px;
}
.popover {
position: absolute;
z-index: 20;
top: 0; left: 0;
background: var(--panel-2);
border: 1px solid #49618e;
border-radius: 12px;
padding: 6px;
box-shadow: 0 18px 40px -14px #000;
animation: pop .13s ease-out;
}
.popover[hidden] { display: none; }
@keyframes pop {
from { opacity: 0; transform: translateY(4px) scale(.96); }
to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
.popover { animation: none; }
.editor { transition: none; }
}
.popover .arrow {
position: absolute;
bottom: -5px;
left: calc(50% - 5px);
width: 9px; height: 9px;
background: var(--panel-2);
border-right: 1px solid #49618e;
border-bottom: 1px solid #49618e;
transform: rotate(45deg);
}
.popover[data-placement="below"] .arrow {
bottom: auto; top: -5px;
transform: rotate(225deg);
}
.row { display: flex; align-items: center; gap: 4px; }
.row[hidden] { display: none; }
.sep { width: 1px; height: 18px; background: var(--line); margin: 0 2px; }
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 500;
color: var(--text);
background: transparent;
border: 0;
border-radius: 8px;
padding: 7px 10px;
cursor: pointer;
}
.btn svg {
width: 15px; height: 15px;
fill: none; stroke: currentColor;
stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.btn:hover { background: #ffffff14; }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.btn[aria-pressed="true"] { background: #78a9ff33; color: #dbe4ff; }
.btn-icon { padding: 7px; color: var(--muted); }
.btn-primary { background: var(--accent); color: #0b1020; font-weight: 600; }
.btn-primary:hover { background: #93bcff; }
.popover input[type="url"] {
width: 230px;
background: #0a0f1c;
border: 1px solid var(--line);
border-radius: 8px;
color: var(--text);
font-size: 13px;
padding: 8px 10px;
}
.popover input[type="url"]:focus { outline: none; border-color: var(--accent); }
.popover input[type="url"]:invalid:not(:placeholder-shown) { border-color: #e5735f; }
.status {
margin: 18px 0 0;
font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
color: var(--muted);
}
.sr-only {
position: absolute; width: 1px; height: 1px;
padding: 0; margin: -1px; overflow: hidden;
clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}/**
* Inline code / link selection popover.
* No execCommand, no dependencies: all formatting is Range surgery on the
* contenteditable DOM, with the selection restored afterwards.
*/
(() => {
const editor = document.getElementById("editor");
const pop = document.getElementById("popover");
const status = document.getElementById("status");
const actions = pop.querySelector('[data-panel="actions"]');
const linkForm = pop.querySelector('[data-panel="link"]');
const urlInput = pop.querySelector("#link-url");
const codeBtn = pop.querySelector('[data-act="code"]');
const linkBtn = pop.querySelector('[data-act="link"]');
/** Range saved while focus moves into the popover (which collapses the selection). */
let saved = null;
/* ---------- selection helpers ---------- */
const sel = () => window.getSelection();
function activeRange() {
const s = sel();
if (!s || s.rangeCount === 0) return null;
const r = s.getRangeAt(0);
if (r.collapsed) return null;
if (!editor.contains(r.commonAncestorContainer)) return null;
return r;
}
function restore(range) {
const s = sel();
s.removeAllRanges();
s.addRange(range);
}
/** Nearest ancestor matching `sel` that is still inside the editor. */
function closestIn(node, selector) {
let el = node && node.nodeType === 1 ? node : node && node.parentElement;
while (el && el !== editor) {
if (el.matches(selector)) return el;
el = el.parentElement;
}
return null;
}
/** True when the whole range sits inside an element matching `selector`. */
function rangeInside(range, selector) {
const a = closestIn(range.startContainer, selector);
return a && a === closestIn(range.endContainer, selector) ? a : null;
}
/* ---------- formatting ---------- */
/** Replace `el` with its own children, keeping the text flow intact. */
function unwrap(el) {
const parent = el.parentNode;
while (el.firstChild) parent.insertBefore(el.firstChild, el);
parent.removeChild(el);
parent.normalize();
}
/** Remove any nested code/anchor wrappers from an extracted fragment. */
function stripInline(fragment) {
fragment.querySelectorAll("code, a").forEach((n) => {
const p = n.parentNode;
while (n.firstChild) p.insertBefore(n.firstChild, n);
p.removeChild(n);
});
return fragment;
}
/**
* Wrap the current range in `tag`. Returns the created element.
* Existing inline wrappers inside the range are flattened first so we never
* produce <code><a><code>… nesting.
*/
function wrapRange(range, tag, attrs) {
const el = document.createElement(tag);
for (const [k, v] of Object.entries(attrs || {})) el.setAttribute(k, v);
el.appendChild(stripInline(range.extractContents()));
range.insertNode(el);
editor.normalize();
const after = document.createRange();
after.selectNodeContents(el);
restore(after);
return el;
}
function toggleCode(range) {
const existing = rangeInside(range, "code");
if (existing) {
const r = document.createRange();
r.selectNodeContents(existing);
unwrap(existing);
restore(r);
report("Removed inline code.");
return;
}
wrapRange(range, "code");
report("Wrapped selection in <code>.");
}
function normalizeUrl(raw) {
const value = raw.trim();
if (!value) return null;
const candidate = /^[a-z][a-z0-9+.-]*:/i.test(value) ? value : `https://${value}`;
let url;
try {
url = new URL(candidate);
} catch {
return null;
}
// Never allow javascript:/data: to reach the DOM.
return /^https?:$/.test(url.protocol) ? url.href : null;
}
function applyLink(range, raw) {
const href = normalizeUrl(raw);
if (!href) {
urlInput.setCustomValidity("Enter a valid http(s) URL");
urlInput.reportValidity();
return false;
}
urlInput.setCustomValidity("");
const existing = rangeInside(range, "a");
if (existing) unwrapKeepingRange(existing);
wrapRange(activeRange() || range, "a", {
href,
rel: "noreferrer noopener",
target: "_blank",
});
report(`Linked to ${href}`);
return true;
}
function unwrapKeepingRange(el) {
const r = document.createRange();
r.selectNodeContents(el);
unwrap(el);
restore(r);
}
function clearFormatting(range) {
const anchor = rangeInside(range, "a");
const code = rangeInside(range, "code");
if (anchor) unwrapKeepingRange(anchor);
if (code) unwrapKeepingRange(code);
if (!anchor && !code) {
// Partial selection spanning several wrappers: flatten the extracted slice.
const span = wrapRange(range, "span");
unwrapKeepingRange(span);
}
report("Cleared inline formatting.");
}
/* ---------- popover placement ---------- */
const GAP = 10;
function place(range) {
const rect = range.getBoundingClientRect();
if (!rect.width && !rect.height) return hide();
pop.hidden = false;
const box = pop.getBoundingClientRect();
const sx = window.scrollX;
const sy = window.scrollY;
let top = rect.top + sy - box.height - GAP;
let placement = "above";
if (rect.top - box.height - GAP < 0) {
top = rect.bottom + sy + GAP;
placement = "below";
}
let left = rect.left + sx + rect.width / 2 - box.width / 2;
const min = sx + 8;
const max = sx + document.documentElement.clientWidth - box.width - 8;
const centered = left;
left = Math.max(min, Math.min(max, left));
pop.dataset.placement = placement;
pop.style.top = `${Math.round(top)}px`;
pop.style.left = `${Math.round(left)}px`;
// Keep the arrow pointing at the selection even after clamping.
const arrow = pop.querySelector(".arrow");
arrow.style.left = `${Math.round(Math.max(8, Math.min(box.width - 17, box.width / 2 + (centered - left) - 5)))}px`;
}
function syncState(range) {
codeBtn.setAttribute("aria-pressed", String(!!rangeInside(range, "code")));
linkBtn.setAttribute("aria-pressed", String(!!rangeInside(range, "a")));
}
function showActions() {
linkForm.hidden = true;
actions.hidden = false;
}
function show(range) {
saved = range.cloneRange();
showActions();
place(range);
syncState(range);
report(`Selected ${range.toString().length} chars.`);
}
function hide() {
pop.hidden = true;
showActions();
urlInput.value = "";
saved = null;
}
function report(msg) {
status.textContent = msg;
}
/* ---------- events ---------- */
document.addEventListener("selectionchange", () => {
if (pop.contains(document.activeElement)) return; // editing the URL field
const r = activeRange();
if (r) show(r);
else if (!pop.hidden) {
hide();
report("No selection.");
}
});
pop.addEventListener("mousedown", (e) => {
// Don't let the toolbar steal (and collapse) the selection.
if (e.target !== urlInput) e.preventDefault();
});
actions.addEventListener("click", (e) => {
const btn = e.target.closest("[data-act]");
if (!btn || !saved) return;
const range = saved.cloneRange();
restore(range);
if (btn.dataset.act === "code") {
toggleCode(range);
} else if (btn.dataset.act === "link") {
const existing = rangeInside(range, "a");
if (existing) {
unwrapKeepingRange(existing);
report("Removed link.");
} else {
actions.hidden = true;
linkForm.hidden = false;
urlInput.value = "";
place(range);
urlInput.focus();
return;
}
} else if (btn.dataset.act === "clear") {
clearFormatting(range);
}
const r = activeRange();
if (r) show(r);
else hide();
});
linkForm.addEventListener("submit", (e) => {
e.preventDefault();
if (!saved) return;
const range = saved.cloneRange();
restore(range);
if (applyLink(range, urlInput.value)) {
hide();
editor.focus();
}
});
linkForm.addEventListener("click", (e) => {
if (e.target.closest('[data-act="cancel"]')) {
const range = saved && saved.cloneRange();
showActions();
if (range) {
restore(range);
place(range);
} else hide();
}
});
urlInput.addEventListener("input", () => urlInput.setCustomValidity(""));
editor.addEventListener("keydown", (e) => {
if (!(e.ctrlKey || e.metaKey)) return;
const key = e.key.toLowerCase();
const range = activeRange();
if (!range) return;
if (key === "e") {
e.preventDefault();
toggleCode(range);
const r = activeRange();
if (r) show(r);
} else if (key === "k") {
e.preventDefault();
show(range);
actions.hidden = true;
linkForm.hidden = false;
place(range);
urlInput.focus();
}
});
document.addEventListener("keydown", (e) => {
if (e.key !== "Escape" || pop.hidden) return;
if (!linkForm.hidden && saved) {
const range = saved.cloneRange();
showActions();
restore(range);
place(range);
editor.focus();
} else {
hide();
report("Dismissed.");
}
});
const reposition = () => {
const r = activeRange();
if (r && !pop.hidden) place(r);
};
window.addEventListener("scroll", reposition, true);
window.addEventListener("resize", reposition);
document.addEventListener("mousedown", (e) => {
if (!pop.contains(e.target) && !editor.contains(e.target)) hide();
});
})();<link rel="stylesheet" href="style.css" />
<main class="demo" data-demo="Inline Code Link Popover">
<header class="head">
<h1>Inline Code & Link Popover</h1>
<p>Select any text in the editor. A popover anchors to the selection with inline formatting
actions. Keyboard: select with <kbd>Shift</kbd>+arrows, then <kbd>Ctrl</kbd>+<kbd>E</kbd> for
code or <kbd>Ctrl</kbd>+<kbd>K</kbd> for a link. <kbd>Esc</kbd> dismisses.</p>
</header>
<div
id="editor"
class="editor"
contenteditable="true"
spellcheck="false"
role="textbox"
aria-multiline="true"
aria-label="Rich text editor"
>
<p>The popover only appears for non-collapsed selections inside this editor. Try selecting the
phrase document.querySelector and turning it into inline code.</p>
<p>Links are validated before insertion and get rel="noreferrer", so a selection like the
stealthis docs can become a real anchor without leaving the keyboard.</p>
</div>
<div id="popover" class="popover" role="toolbar" aria-label="Selection formatting" hidden>
<div class="row" data-panel="actions">
<button type="button" class="btn" data-act="code" aria-pressed="false" title="Inline code (Ctrl+E)">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6 3 12l6 6M15 6l6 6-6 6"/></svg>
<span>Code</span>
</button>
<button type="button" class="btn" data-act="link" aria-pressed="false" title="Link (Ctrl+K)">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/></svg>
<span>Link</span>
</button>
<span class="sep" aria-hidden="true"></span>
<button type="button" class="btn btn-icon" data-act="clear" title="Remove inline formatting" aria-label="Remove inline formatting">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M7 7l1 12h8l1-12M10 11v5M14 11v5"/></svg>
</button>
</div>
<form class="row" data-panel="link" hidden>
<label class="sr-only" for="link-url">Link URL</label>
<input id="link-url" type="url" placeholder="https://example.com" autocomplete="off" required />
<button type="submit" class="btn btn-primary">Apply</button>
<button type="button" class="btn btn-icon" data-act="cancel" aria-label="Cancel link">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
</form>
<div class="arrow" aria-hidden="true"></div>
</div>
<p class="status" id="status" role="status" aria-live="polite">No selection.</p>
</main>
<script src="script.js"></script>import { useState } from "react";
export function RichEditorInlinePopover() {
const [value, setValue] = useState("");
return (
<section className="demo">
<h2>Inline Code Link Popover</h2>
<div
contentEditable
suppressContentEditableWarning
onInput={(event) => setValue(event.currentTarget.textContent ?? "")}
style={{ minHeight: 140, outline: "1px solid #39527d", padding: 16 }}
/>{" "}
<p>{value.length} characters</p>
</section>
);
}Inline Code Link Popover
Apply inline code or a link to a text selection using a small selection popover.
Support notes
These editors stay intentionally dependency-free. Treat contenteditable value as untrusted, sanitize before persistence, and replace execCommand with a model-driven editor when requirements grow.
Included demo
- Vanilla HTML, CSS, and JavaScript with zero external dependencies.
- React equivalent using the same interaction model.
- A Lab route for trying the behavior in isolation.
Integration checklist
Keep state updates separate from presentation, preserve semantic labels, and add persistence or server callbacks at the boundary where your product needs them.