Contextual Toolbar

Toolbar appearing for a current selection/context.

Great writing starts with clear design tokens and a consistent voice throughout the document.

html

<div class="ctb-demo">
  <div class="ctb-wrap">
    <p class="ctb-text">Great writing starts with clear <span class="ctb-selection">design tokens</span> and a consistent voice throughout the document.</p>
    
    <div class="ctb-toolbar ctb-toolbar--above ctb-toolbar--dark" role="toolbar" aria-label="Text formatting">
      <button type="button" class="ctb-btn" aria-label="Bold" style="color:#E8A020;"><strong>B</strong></button>
      <button type="button" class="ctb-btn" aria-label="Italic" style="color:#E8A020;"><em>I</em></button>
      <span class="ctb-divider" aria-hidden="true"></span>
      <button type="button" class="ctb-btn" aria-label="Add link" style="color:#E8A020;">
        <svg viewBox="0 0 20 20" width="15" height="15" aria-hidden="true"><path d="M8.5 11.5 11.5 8.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M9.5 6.5 11 5a2.5 2.5 0 0 1 3.5 3.5L13 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M10.5 13.5 9 15a2.5 2.5 0 0 1-3.5-3.5L7 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
      </button>
      <button type="button" class="ctb-btn" aria-label="Add comment" style="color:#E8A020;">
        <svg viewBox="0 0 20 20" width="15" height="15" aria-hidden="true"><path d="M3 4.5h14v9H8l-3 3v-3H3z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
      </button>
    </div>
    
  </div>
</div>
<style>
.ctb-demo { display: grid; place-items: center; min-height: 260px; border-radius: 12px; padding: 24px; box-sizing: border-box; }
.ctb-wrap { position: relative; max-width: 420px; font: 15px/1.7 system-ui, sans-serif; color: #241430; }
.ctb-text { margin: 0; }
.ctb-selection { background: #BFD9F255; border-radius: 3px; padding: 0 2px; }
.ctb-toolbar { position: absolute; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 4px; padding: 6px 8px; border-radius: 10px; box-shadow: 0 10px 24px rgba(36,20,48,.28); }
.ctb-toolbar--above { bottom: calc(100% + 10px); }
.ctb-toolbar--below { top: calc(100% + 10px); }
.ctb-toolbar--light { background: #FAF7F2; }
.ctb-toolbar--dark { background: #241430; }
.ctb-toolbar--brand { background: #D9C8F0; }
.ctb-btn { background: none; border: none; cursor: pointer; font: 600 13px system-ui, sans-serif; padding: 5px 8px; border-radius: 6px; display: grid; place-items: center; }
.ctb-btn:hover, .ctb-btn:focus-visible { background: rgba(255,255,255,.16); outline: none; }
.ctb-divider { width: 1px; height: 16px; background: currentColor; opacity: .25; }
</style>

tailwind

<div class="grid place-items-center min-h-[260px] rounded-xl p-6 box-border">
  <div class="relative max-w-[420px] font-sans text-[15px] leading-[1.7] text-[#241430]">
    <p class="m-0">Great writing starts with clear <span class="bg-[#BFD9F2]/30 rounded-[3px] px-0.5">design tokens</span> and a consistent voice throughout the document.</p>
    
    <div role="toolbar" aria-label="Text formatting" class="ctb-tw ctb-tw--above ctb-tw--dark absolute left-1/2 -translate-x-1/2 flex items-center gap-1 px-2 py-1.5 rounded-[10px] shadow-2xl">
      <button type="button" aria-label="Bold" class="bg-transparent border-none cursor-pointer font-semibold text-[13px] px-2 py-1 rounded-md grid place-items-center" style="color:#E8A020;"><strong>B</strong></button>
      <button type="button" aria-label="Italic" class="bg-transparent border-none cursor-pointer font-semibold text-[13px] px-2 py-1 rounded-md grid place-items-center" style="color:#E8A020;"><em>I</em></button>
      <span aria-hidden="true" class="w-px h-4 bg-current opacity-25"></span>
      <button type="button" aria-label="Add link" class="bg-transparent border-none cursor-pointer px-2 py-1 rounded-md grid place-items-center" style="color:#E8A020;">
        <svg viewBox="0 0 20 20" width="15" height="15" aria-hidden="true"><path d="M8.5 11.5 11.5 8.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M9.5 6.5 11 5a2.5 2.5 0 0 1 3.5 3.5L13 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M10.5 13.5 9 15a2.5 2.5 0 0 1-3.5-3.5L7 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
      </button>
      <button type="button" aria-label="Add comment" class="bg-transparent border-none cursor-pointer px-2 py-1 rounded-md grid place-items-center" style="color:#E8A020;">
        <svg viewBox="0 0 20 20" width="15" height="15" aria-hidden="true"><path d="M3 4.5h14v9H8l-3 3v-3H3z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
      </button>
    </div>
    
  </div>
</div>
<style>
.ctb-tw--above { bottom: calc(100% + 10px); }
.ctb-tw--below { top: calc(100% + 10px); }
.ctb-tw--light { background: #FAF7F2; }
.ctb-tw--dark { background: #241430; }
.ctb-tw--brand { background: #D9C8F0; }
</style>

react

export function ContextualToolbar({
  selectedText = "design tokens",
  label = "Text formatting",
  variant = "dark",
  accent = "#E8A020",
  placement = "above",
  visible = true,
}) {
  const bg = { light: "#FAF7F2", dark: "#241430", brand: "#D9C8F0" }[variant] ?? "#241430";
  const pos = placement === "below" ? { top: "calc(100% + 10px)" } : { bottom: "calc(100% + 10px)" };
  return (
    <div style={{ display: "grid", placeItems: "center", minHeight: 260, borderRadius: 12, padding: 24, boxSizing: "border-box" }}>
      <div style={{ position: "relative", maxWidth: 420, font: "15px/1.7 system-ui, sans-serif", color: "#241430" }}>
        <p style={{ margin: 0 }}>
          Great writing starts with clear{" "}
          <span style={{ background: "#BFD9F255", borderRadius: 3, padding: "0 2px" }}>{selectedText}</span>{" "}
          and a consistent voice throughout the document.
        </p>
        {visible && (
          <div role="toolbar" aria-label={label} style={{ position: "absolute", left: "50%", transform: "translateX(-50%)", display: "flex",
            alignItems: "center", gap: 4, padding: "6px 8px", borderRadius: 10, boxShadow: "0 10px 24px rgba(36,20,48,.28)", background: bg, ...pos }}>
            <button type="button" aria-label="Bold" style={{ background: "none", border: "none", cursor: "pointer", fontWeight: 600, fontSize: 13, padding: "5px 8px", borderRadius: 6, color: accent }}><strong>B</strong></button>
            <button type="button" aria-label="Italic" style={{ background: "none", border: "none", cursor: "pointer", fontWeight: 600, fontSize: 13, padding: "5px 8px", borderRadius: 6, color: accent }}><em>I</em></button>
            <span aria-hidden="true" style={{ width: 1, height: 16, background: "currentColor", opacity: .25 }} />
            <button type="button" aria-label="Add link" style={{ background: "none", border: "none", cursor: "pointer", padding: "5px 8px", borderRadius: 6, color: accent, display: "grid", placeItems: "center" }}>
              <svg viewBox="0 0 20 20" width="15" height="15" aria-hidden="true">
                <path d="M8.5 11.5 11.5 8.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
                <path d="M9.5 6.5 11 5a2.5 2.5 0 0 1 3.5 3.5L13 10" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
                <path d="M10.5 13.5 9 15a2.5 2.5 0 0 1-3.5-3.5L7 10" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
              </svg>
            </button>
            <button type="button" aria-label="Add comment" style={{ background: "none", border: "none", cursor: "pointer", padding: "5px 8px", borderRadius: 6, color: accent, display: "grid", placeItems: "center" }}>
              <svg viewBox="0 0 20 20" width="15" height="15" aria-hidden="true">
                <path d="M3 4.5h14v9H8l-3 3v-3H3z" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round" />
              </svg>
            </button>
          </div>
        )}
      </div>
    </div>
  );
}