Skip to content

Overlays

Things that float above the page — modal, dropdown, tooltip, drawer. Zero JavaScript, except the one honest line the modal and drawer need.

Modal

The native dialog is keel's modal — there is no .k-modal class because none is needed. The base layer styles the bare element: surface, radius, shadow, padding, and a backdrop that fades in via @starting-style (and degrades to a plain show in browsers without it). The platform does the hard parts for free: focus is trapped inside the dialog, Esc closes it, and a button in a form method="dialog" closes it with no script at all. Honestly: opening it is the one line of JavaScript below — showModal(). For a modal that docks to the screen edge instead of the middle, see the Drawer — same element, one class.

Delete this draft?

The draft and its two attachments are removed. This can't be undone.

Markup
<button class="k-btn" type="button"
        onclick="document.getElementById('confirm').showModal()">Open modal</button>

<dialog id="confirm" aria-labelledby="confirm-title">
  <h3 id="confirm-title">Delete this draft?</h3>
  <p>The draft and its two attachments are removed.</p>
  <form method="dialog" class="k-cluster">
    <button class="k-btn" value="confirm">Delete draft</button>
    <button class="k-btn k-btn--ghost" value="cancel">Cancel</button>
  </form>
</dialog>

Dropdown

.k-dropdown is a details element wearing a .k-btn--ghost styled summary, with a .k-menu list that floats below when open. It works with zero JavaScript. The honest trade-off: it closes when you click the summary again or toggle it from the keyboard, but it does not light-dismiss on an outside click — that's what the missing script would buy you. Menu items are links or real buttons; an hr between items draws a separator.

Options ▾
Markup
<details class="k-dropdown">
  <summary class="k-btn k-btn--ghost">Options ▾</summary>
  <ul class="k-menu">
    <li><a href="rename.html">Rename</a></li>
    <li><a href="duplicate.html">Duplicate</a></li>
    <li><hr></li>
    <li><button type="button">Delete</button></li>
  </ul>
</details>

Menu items

A .k-menu item is more than a link. A .k-menu__label is a small section header; .k-menu__hint pushes a shortcut or a submenu chevron to the trailing edge. A leading .k-icon lines up like a letter. Mark the chosen item aria-current="true" for a single choice or aria-checked="true" for a checkable one — both tint it in the accent, so a menu of aria-checked options is a multi-select picker. .is-danger reddens a destructive item; a real [disabled] button (or aria-disabled) dims and blocks it. As ever, keel styles the states — your JS toggles the attributes.

  • Actions

Markup
<ul class="k-menu">
  <li class="k-menu__label">Actions</li>
  <li><button type="button">
    <span class="k-icon k-icon--edit" aria-hidden="true"></span> Edit
    <span class="k-menu__hint">⌘E</span>
  </button></li>
  <li><button type="button" aria-checked="true">
    <span class="k-icon k-icon--check" aria-hidden="true"></span> Pinned
  </button></li>
  <li><button type="button" disabled>… Share (offline)</button></li>
  <li><hr></li>
  <li><button type="button" class="is-danger">
    <span class="k-icon k-icon--trash" aria-hidden="true"></span> Delete
    <span class="k-menu__hint">⌫</span>
  </button></li>
</ul>

Tooltip

Put text in a data-k-tip attribute and it shows as a small dark bubble on hover and on :focus-visible, so keyboard users see it too. It's pure CSS — which also means the attribute content is not exposed to assistive tech. If the tip carries meaning the visible text doesn't, repeat it in an aria-label. For anything richer than one short line, use a popover instead.

Markup
<!-- visible text already says what it does; the tip adds detail -->
<button class="k-btn k-btn--ghost"
        data-k-tip="Copies a share link to the clipboard">Copy link</button>

<!-- icon-only: the tip is the label, so mirror it in aria-label -->
<button class="k-btn k-btn--ghost"
        data-k-tip="Download as PDF"
        aria-label="Download as PDF">⤓</button>

Popover

.k-popover is the tooltip's richer cousin: a styled floating surface — border, radius, shadow, padding — built on the native popover API. Where the Tooltip is one line of hover text, a popover holds real content: a heading, a paragraph, links. A trigger button carries popovertarget and the panel carries the popover attribute, so opening it, light-dismiss on an outside click, and Esc all come from the platform — zero JavaScript. Honestly: keel ships the panel look, not the tether. An un-anchored popover is centred in the top layer by the browser; to pin it to its trigger, use CSS anchor positioning where it's supported, or the one line of JS that does the same. That placement is the trade-off you accept for the free dismiss behaviour.

Export format

Downloads a self-contained HTML file. Styles are inlined, so it opens anywhere with no server.

Read more about exports

Markup
<button class="k-btn k-btn--ghost" popovertarget="pop">Details</button>

<div id="pop" popover class="k-popover">
  <strong>Export format</strong>
  <p>Downloads a self-contained HTML file.</p>
  <p><a href="exports.html">Read more about exports</a></p>
</div>

Drawer

.k-drawer is a dialog that slides in from the edge instead of appearing in the middle — offcanvas navigation, a settings panel, a cart. --start slides it from the start edge instead of the end. Honestly: opening it is the one line of JavaScript below — showModal() — keel ships the look and the motion. Because it's a real modal dialog, Esc closes it, focus is trapped, and the backdrop comes free; a form method="dialog" button closes it with no script at all.

Menu

Markup
<button class="k-btn k-btn--ghost" type="button"
        onclick="document.getElementById('drawer').showModal()">Open drawer</button>

<dialog class="k-drawer" id="drawer" aria-label="Menu">
  <h4>Menu</h4>
  <ul class="k-menu">
    <li><a href="docs.html">Docs</a></li>
    <li><a href="pricing.html">Pricing</a></li>
  </ul>
  <form method="dialog">
    <button class="k-btn k-btn--ghost">Close</button>
  </form>
</dialog>

<!-- slide from the other edge -->
<dialog class="k-drawer k-drawer--start">…</dialog>