Skip to content
Corner bubbleSource included

Cobalt

Deep navy with a clear blue accent. The safe corporate pick.

NNorthwind SupplyAI assistant
Do you ship to Germany?
Yes — standard delivery is 3–5 working days, and free over €60.

Live preview · real tokens, not a screenshot

When to pick it

Blue is the least surprising colour in software, and for finance, insurance and B2B SaaS that is the point — the widget should look like it belongs to the company, not like a bolt-on. Cobalt is the design to pick when nobody will be asked to approve something adventurous.

How it is built

The navy is desaturated toward slate so it does not fight a brand blue sitting elsewhere on the page. Two blues on one screen clash; a slate-navy and a brand blue do not.

Accessibility

13.6:1 for body text. The blue accent passes as a fill with near-black text on top at 9.2:1.

Every design in this gallery states that it is an AI assistant at the start of the conversation and honours prefers-reduced-motion. Neither is configurable, because both are obligations rather than preferences.

Good fit for

  • Fintech
  • B2B SaaS
  • Insurance
  • Enterprise

Source code · MIT

Copy it and go.

Self-contained: no build step, no dependency, no ChatBrace account. Point send() at your own endpoint, or at ours. MIT licensed.

cobalt-chatbox.html6.6 kb
<!-- Cobalt — chat widget. MIT licensed. -->
<div id="cb" data-open="false">
  <button id="cb-launch" aria-expanded="false" aria-controls="cb-panel">Chat</button>

  <div id="cb-panel" role="dialog" aria-modal="false" aria-label="Chat with Northwind Supply" hidden>
    <header id="cb-head">
      <span id="cb-avatar" aria-hidden="true">N</span>
      <span>
        <strong>Northwind Supply</strong>
        <!-- Required: people must be told they are talking to an AI
             before the conversation starts. Do not remove this line. -->
        <small>AI assistant</small>
      </span>
      <button id="cb-close" aria-label="Close chat">&times;</button>
    </header>

    <div id="cb-log" role="log" aria-live="polite" aria-atomic="false"></div>

    <form id="cb-form">
      <label class="cb-sr" for="cb-input">Your message</label>
      <input id="cb-input" autocomplete="off" placeholder="Ask a question" maxlength="500" />
      <button type="submit" aria-label="Send message">&uarr;</button>
    </form>
  </div>
</div>

<style>
  #cb { --bg:#0e1424; --surface:#151d31; --border:#222d47;
        --text:#e6ecf7; --muted:#8b9ab5; --accent:#60a5fa;
        --on-accent:#0b1120; --bot-bg:#18223a; --bot-text:#e6ecf7;
        --radius:20px; --bubble:18px;
        --ease:cubic-bezier(.16,1,.3,1);
        position:fixed; inset-block-end:24px; inset-inline-end:24px; z-index:2147483000;
        font-family:ui-sans-serif, system-ui, -apple-system, sans-serif; }

  .cb-sr { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); }

  #cb-launch { all:unset; box-sizing:border-box; cursor:pointer; display:grid; place-items:center;
    min-width:52px; height:52px; padding:0 18px; border-radius:999px;
    background:var(--accent); color:var(--on-accent); font-size:14px; font-weight:600;
    box-shadow:0 8px 24px -8px rgba(0,0,0,.35); transition:transform .2s var(--ease); }
  #cb-launch:hover { transform:translateY(-2px); }
  #cb-launch:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }

  #cb-panel { position:absolute; inset-block-end:64px; inset-inline-end:0;
    width:min(380px, calc(100vw - 32px)); background:var(--bg);
    border:1px solid var(--border); border-radius:var(--radius);
    box-shadow:0 8px 24px -8px rgba(0,0,0,.35); overflow:hidden;
    animation:cb-in .28s var(--ease) both; }

  #cb-head { display:flex; align-items:center; gap:10px; padding:11px 14px;
    background:var(--surface); border-bottom:1px solid var(--border); }
  #cb-head strong { display:block; font-size:13px; color:var(--text); }
  #cb-head small { display:block; font-size:11px; color:var(--muted); }
  #cb-avatar { width:26px; height:26px; flex:0 0 auto; display:grid; place-items:center;
    border-radius:999px;
    background:var(--accent); color:var(--on-accent); font-size:11px; font-weight:600; }
  #cb-close { all:unset; cursor:pointer; margin-inline-start:auto; padding:4px 8px;
    color:var(--muted); font-size:18px; line-height:1; }
  #cb-close:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

  #cb-log { display:flex; flex-direction:column; gap:8px; padding:14px;
    height:300px; overflow-y:auto; }
  .cb-msg { max-width:88%; padding:8px 11px; font-size:13px; line-height:1.5;
    border-radius:var(--bubble); animation:cb-msg .3s var(--ease) both; }
  .cb-user { align-self:flex-end; background:var(--accent); color:var(--on-accent); }
  .cb-bot  { align-self:flex-start; background:var(--bot-bg); color:var(--bot-text);
              }

  #cb-form { display:flex; align-items:center; gap:8px; padding:10px 14px;
    background:var(--surface); border-top:1px solid var(--border); }
  #cb-input { flex:1; min-width:0; background:none; border:none; outline:none;
    color:var(--text); font:inherit; font-size:13px; }
  #cb-input::placeholder { color:var(--muted); }
  #cb-form button { all:unset; cursor:pointer; width:28px; height:28px; display:grid;
    place-items:center; border-radius:999px;
    background:var(--accent); color:var(--on-accent); font-size:14px; }
  #cb-form button:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

  @keyframes cb-in  { from{opacity:0; transform:translateY(8px) scale(.98)} to{opacity:1; transform:none} }
  @keyframes cb-msg { from{opacity:0; transform:translateY(6px)} to{opacity:1; transform:none} }

  /* Vestibular safety: motion is removed, not shortened. */
  @media (prefers-reduced-motion: reduce) {
    #cb-panel, .cb-msg { animation:none; }
    #cb-launch { transition:none; }
    #cb-launch:hover { transform:none; }
  }
</style>

<script>
(function () {
  var root   = document.getElementById('cb');
  var panel  = document.getElementById('cb-panel');
  var launch = document.getElementById('cb-launch');
  var close  = document.getElementById('cb-close');
  var form   = document.getElementById('cb-form');
  var input  = document.getElementById('cb-input');
  var log    = document.getElementById('cb-log');
  var conversationId = null;

  function toggle(open) {
    panel.hidden = !open;
    root.dataset.open = String(open);
    launch.setAttribute('aria-expanded', String(open));
    if (open) input.focus(); else launch.focus();
  }

  function add(text, who) {
    var el = document.createElement('div');
    el.className = 'cb-msg ' + (who === 'user' ? 'cb-user' : 'cb-bot');
    el.textContent = text;              // textContent, never innerHTML
    log.appendChild(el);
    log.scrollTop = log.scrollHeight;
    return el;
  }

  launch.addEventListener('click', function () { toggle(panel.hidden); });
  close.addEventListener('click', function () { toggle(false); });
  document.addEventListener('keydown', function (e) {
    if (e.key === 'Escape' && !panel.hidden) toggle(false);
  });

  form.addEventListener('submit', function (e) {
    e.preventDefault();
    var text = input.value.trim();
    if (!text) return;
    add(text, 'user');
    input.value = '';
    send(text);
  });

  // Point this at your own endpoint, or at ChatBrace.
  // Never send conversation history from the client — the server owns it.
  function send(message) {
    var pending = add('…', 'bot');
    fetch('https://api.chatbrace.com/api/widget/chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        widgetKey: 'YOUR_WIDGET_KEY',
        conversationId: conversationId,
        message: message
      })
    })
      .then(function (r) { return r.json(); })
      .then(function (data) {
        conversationId = data.conversationId || conversationId;
        pending.textContent = data.reply || 'Sorry — something went wrong.';
      })
      .catch(function () {
        pending.textContent = 'Sorry — I could not reach the server.';
      });
  }
})();
</script>

Other corner bubble designs

Put it on your site
before you finish your coffee.

A hundred free messages, no card, and a playground to try it in before you touch a single line of your own markup.