Message Composer

Interface for composing a message.

280 character limit

html

<form class="composer composer--expanded" data-variant="expanded">
  <label class="composer__label" for="composer-input">Message</label>
  <textarea id="composer-input" class="composer__input" placeholder="Write a message…" rows="3">Let&#39;s ship the update on Friday.</textarea>
  <div class="composer__toolbar">
    
    <button type="button" class="composer__icon-btn" aria-label="Attach file">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M7 12v6a3 3 0 0 0 6 0V8a5 5 0 0 0-10 0v9a7 7 0 0 0 14 0V10"/></svg>
    </button>
    
    <span class="composer__count">280 character limit</span>
    <button type="button" class="composer__send">Send</button>
  </div>
</form>
<style>
.composer { font: 14px/1.5 system-ui, sans-serif; max-width: 420px; box-sizing: border-box; background: #FAF7F2; color: #241430; border: 1px solid #24143026; border-radius: 12px; padding: 16px; }
.composer--compact { padding: 8px; border-radius: 8px; }
.composer--expanded { padding: 20px; }
.composer--threaded { padding: 16px 16px 16px 13px; border-left: 3px solid #A8E6C1; border-radius: 4px 12px 12px 4px; }
.composer__label { display: block; font-weight: 600; margin-bottom: 6px; font-size: 13px; }
.composer__input { width: 100%; box-sizing: border-box; font: inherit; border: 1px solid #24143033; border-radius: 8px; padding: 10px; resize: vertical; background: #ffffff; color: #241430; }
.composer__input:focus-visible { outline: 2px solid #241430; outline-offset: 1px; }
.composer__toolbar { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.composer__icon-btn { border: none; background: transparent; color: #241430; cursor: pointer; padding: 6px; border-radius: 6px; display: inline-flex; }
.composer__icon-btn:hover { background: #24143014; }
.composer__icon-btn:focus-visible { outline: 2px solid #241430; outline-offset: 1px; }
.composer__count { font-size: 12px; color: #24143099; flex: 1; }
.composer__send { margin-left: auto; background: #241430; color: #FAF7F2; border: none; border-radius: 8px; padding: 8px 16px; font: 600 13px system-ui, sans-serif; cursor: pointer; }
.composer__send:hover { opacity: .9; }
.composer__send:focus-visible { outline: 2px solid #A8E6C1; outline-offset: 2px; }
</style>

tailwind

<form class="composer-tw composer-tw--expanded max-w-[420px] box-border bg-[#FAF7F2] text-[#241430] border border-[#24143026] rounded-xl p-4" data-variant="expanded">
  <label class="block font-semibold mb-1.5 text-[13px]" for="composer-tw-input">Message</label>
  <textarea id="composer-tw-input" rows="3" placeholder="Write a message…" class="w-full box-border font-sans border border-[#24143033] rounded-lg p-2.5 bg-white text-[#241430] resize-y">Let&#39;s ship the update on Friday.</textarea>
  <div class="flex items-center gap-2.5 mt-2.5">
    
    <button type="button" aria-label="Attach file" class="border-0 bg-transparent text-[#241430] cursor-pointer p-1.5 rounded-md inline-flex hover:bg-[#24143014]">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M7 12v6a3 3 0 0 0 6 0V8a5 5 0 0 0-10 0v9a7 7 0 0 0 14 0V10"/></svg>
    </button>
    
    <span class="text-xs text-[#24143099] flex-1">280 character limit</span>
    <button type="button" class="ml-auto bg-[#241430] text-[#FAF7F2] border-0 rounded-lg px-4 py-2 text-[13px] font-semibold cursor-pointer">Send</button>
  </div>
</form>
<style>
.composer-tw--compact { padding: 8px !important; border-radius: 8px !important; }
.composer-tw--expanded { padding: 20px !important; }
.composer-tw--threaded { padding: 16px 16px 16px 13px !important; border-left: 3px solid #A8E6C1 !important; border-radius: 4px 12px 12px 4px !important; }
</style>

react

export function MessageComposer({
  placeholder = "Write a message…",
  draft = "Let&#39;s ship the update on Friday.",
  variant = "expanded",
  attachEnabled = true,
  charLimit = 280,
}) {
  const pad = { compact: 8, expanded: 20, threaded: 16 }[variant] ?? 16;
  const border = variant === "threaded" ? "3px solid #A8E6C1" : "1px solid #24143026";
  return (
    <form style={{ font: "14px/1.5 system-ui, sans-serif", maxWidth: 420, boxSizing: "border-box",
      background: "#FAF7F2", color: "#241430", borderRadius: 12, padding: pad,
      borderLeft: variant === "threaded" ? border : undefined,
      border: variant === "threaded" ? undefined : border }}>
      <label htmlFor="mc-input" style={{ display: "block", fontWeight: 600, marginBottom: 6, fontSize: 13 }}>Message</label>
      <textarea id="mc-input" rows={3} placeholder={placeholder} defaultValue={draft}
        style={{ width: "100%", boxSizing: "border-box", font: "inherit", border: "1px solid #24143033",
          borderRadius: 8, padding: 10, background: "#fff", color: "#241430" }} />
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 10 }}>
        {attachEnabled && (
          <button type="button" aria-label="Attach file" style={{ border: "none", background: "transparent", color: "#241430", cursor: "pointer", padding: 6, borderRadius: 6, display: "inline-flex" }}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M7 12v6a3 3 0 0 0 6 0V8a5 5 0 0 0-10 0v9a7 7 0 0 0 14 0V10" /></svg>
          </button>
        )}
        <span style={{ fontSize: 12, color: "#24143099", flex: 1 }}>{charLimit} character limit</span>
        <button type="button" style={{ marginLeft: "auto", background: "#241430", color: "#FAF7F2", border: "none", borderRadius: 8, padding: "8px 16px", fontSize: 13, fontWeight: 600, cursor: "pointer" }}>Send</button>
      </div>
    </form>
  );
}