Components / Layout & Navigation / Speed Dial

Speed Dial beta

One floating button that fans out into the few actions a page is really for.

Preview

The corner of an app. The button below is the part.

<div style="position:relative;height:290px;border:1px solid var(--k-line);border-radius:14px;overflow:hidden;background:var(--k-bg-soft)">
  <p style="position:absolute;left:16px;top:14px;font-size:12.5px;color:var(--k-ink-3)">The corner of an app. The button below is the part.</p>
  <div class="k-dial" data-dial>
    <div class="acts">
      <button class="act" type="button"><span class="lbl">New document</span><span class="fab"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5Z"/><path d="M14 3v5h5M9 13h6M9 17h6"/></svg></span></button>
      <button class="act" type="button"><span class="lbl">New photo</span><span class="fab"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="10" r="1.6"/><path d="m5 19 5.5-5.5 3 3L17 13l2 2"/></svg></span></button>
      <button class="act" type="button"><span class="lbl">Invite people</span><span class="fab"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><circle cx="9" cy="8.5" r="3.4"/><path d="M2.8 20c.7-3.4 3.2-5.2 6.2-5.2s5.5 1.8 6.2 5.2"/><path d="M16 5.4a3.4 3.4 0 0 1 0 6.4M18.5 15.4c1.5.8 2.5 2.4 2.9 4.6"/></svg></span></button>
    </div>
    <button class="main" type="button" aria-label="Create" aria-expanded="false" aria-haspopup="true"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg></button>
  </div>
</div>
/* KATA Speed Dial — one button, a fan of actions. */
.k-dial{position:absolute;right:18px;bottom:18px;display:flex;flex-direction:column;align-items:flex-end;gap:10px;font-family:var(--k-font)}
.k-dial .acts{display:flex;flex-direction:column;align-items:flex-end;gap:9px;opacity:0;transform:translateY(8px);pointer-events:none;transition:opacity .18s,transform .18s}
.k-dial.open .acts{opacity:1;transform:none;pointer-events:auto}
.k-dial .act{display:flex;align-items:center;gap:9px;background:none;border:0;cursor:pointer;font-family:inherit;padding:0}
.k-dial .act .lbl{font-size:12.5px;font-weight:550;color:var(--k-ink);background:var(--k-bg);border:1px solid var(--k-line);border-radius:8px;padding:5px 10px;box-shadow:0 2px 8px rgba(16,24,40,.08)}
.k-dial .act .fab{width:42px;height:42px;border-radius:50%;background:var(--k-bg);border:1px solid var(--k-line);color:var(--k-ink-2);display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px rgba(16,24,40,.1)}
.k-dial .act:hover .fab{background:var(--k-bg-soft);color:var(--k-ink)}
.k-dial .main{width:54px;height:54px;border-radius:50%;background:var(--k-ink);color:var(--k-bg);border:0;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 6px 18px rgba(16,24,40,.28)}
.k-dial .main svg{transition:transform .2s}
.k-dial.open .main svg{transform:rotate(45deg)}
@media (prefers-reduced-motion:reduce){.k-dial .acts,.k-dial .main svg{transition:none}}
// KATA Speed Dial — open and shut; Escape shuts it from anywhere.
document.querySelectorAll('[data-dial]').forEach(function (el) {
  var main = el.querySelector('.main');
  main.addEventListener('click', function () {
    var open = el.classList.toggle('open');
    main.setAttribute('aria-expanded', open ? 'true' : 'false');
  });
  document.addEventListener('keydown', function (e) {
    if (e.key === 'Escape' && el.classList.contains('open')) {
      el.classList.remove('open');
      main.setAttribute('aria-expanded', 'false');
    }
  });
});

Installation

CLI

node kata-ui.mjs add speed-dial

Writes the part’s CSS and JS into your project. New to the CLI? See the CLI doc.

Manual

  1. Load tokens.css once, before any part.
  2. Copy the CSS tab into your stylesheet.
  3. Copy the HTML where it belongs. The part has a script — include it after the markup; it initializes itself.

Usage

For app-shaped pages where one verb ("add", "compose") has variants: the button rests at the corner, the actions rise with their labels showing. Three to five actions — a sixth is a menu wearing a costume. Fixing it to the viewport is the page’s job; the part is the cluster.

Accessibility

The trigger carries aria-expanded and the actions are ordinary buttons, reachable by keyboard in order when open. Labels are text, not tooltips — the name of each action is visible, not hovered into existence.

Anatomy

The public surface of this part, read from its own source. Class names are the API — the CSS tab above is the full contract.

.k-dialclass
.actsclass
.openclass
.actclass
.lblclass
.fabclass
.mainclass
data-dialdata hook
aria-hiddenARIA
aria-labelARIA
aria-expandedARIA
aria-haspopupARIA

Related parts