Components / Layout & Navigation / Speed Dial
Speed Dial beta
One floating button that fans out into the few actions a page is really for.
<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
- Load
tokens.cssonce, before any part. - Copy the CSS tab into your stylesheet.
- 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-dial | class |
.acts | class |
.open | class |
.act | class |
.lbl | class |
.fab | class |
.main | class |
data-dial | data hook |
aria-hidden | ARIA |
aria-label | ARIA |
aria-expanded | ARIA |
aria-haspopup | ARIA |
Related parts
Accordion beta
Disclosure built on native <details>. Zero JavaScript, keyboard and find-in-page included.
Bottom Navigation stable
The mobile app tab bar: three to five destinations under the thumb, the current one lit with the core.
Breadcrumbs beta
Where you are, as a path. The last crumb is the page itself — not a link.
Collapsible stable
One disclosure on its own: a trigger row and the region it folds away. The Accordion is this, grouped.