Flyout

Positioned surface attached to a trigger, commonly used for action lists and small panels.

Flyout and context menu share one action-surface runtime:

  • Flyout = a visible trigger opens a surface.
  • Context menu = right click or a keyboard context action opens that same surface.
  • Sheet = mobile presentation mode of that same surface.

Use data-flyout-mobile on the flyout to control mobile behavior:

  • auto is the default. It keeps the surface anchored on desktop and switches to a bottom sheet on coarse pointers below the breakpoint.
  • sheet always uses the sheet presentation.
  • anchored always uses floating positioning.
  • none disables the mobile transformation.

data-flyout-breakpoint only matters for auto. Prefer the built-in tokens (sm, md, lg) and use raw pixel values only as an escape hatch, for example data-flyout-breakpoint="640". Use data-flyout-placement for the preferred anchored placement. It accepts the placement strings supported by the floating runtime, such as bottom-start, bottom-end, top-start, right, or left. Use data-flyout-distance for the trigger gap in pixels. The default is 4. Use data-flyout-auto-close when the default click dismissal behavior is not right. Escape still follows the shared surface lifecycle.

  • true is the default. Action items close on activation and outside clicks close the flyout.
  • inside closes on action item activation only.
  • outside closes on outside click only.
  • false disables automatic click closing.

Flyout covers two distinct patterns:

Action list

  • A list of actions the user can take: sign out, copy, delete.
  • Wrap the trigger and flyout in .flyout-trigger when the flyout should have a local absolute-position fallback before JavaScript positions it. Add .stretch when the trigger must span its container, such as the last row of a full-width sidebar nav list.
  • Use <menu class="flyout"> with <li> items.
  • Items are regular <button> or <a> elements.
  • Use .sm or .lg for density changes.
  • Direct action items get roving arrow-key focus.
  • Add role="menu" / role="menuitem" only when you intentionally need the ARIA menu pattern.

Nav panel

  • A panel of links to other pages: product categories, docs sections.
  • Items are regular <a href> links, not role="menuitem".
  • No arrow-key navigation, no role="menu".
  • Just a toggle with outside-click and Escape dismissal.
  • Use grid utilities, such as .grid-3, for wider multi-column flyouts.
<div class="flyout-trigger">
  <button class="btn outline"
          type="button"
          aria-expanded="false"
          aria-controls="account-actions"
          id="account-flyout-trigger">
    Account
    <i class="ti ti-chevron-down" aria-hidden="true"></i>
  </button>

  <menu class="flyout sm"
        id="account-actions"
        aria-labelledby="account-flyout-trigger"
        hidden>
    <li><button type="button">Profile</button></li>
    <li><button type="button">Settings</button></li>
    <li><hr /></li>
    <li><button class="danger" type="button">Sign out</button></li>
  </menu>
</div>
<nav aria-label="Main navigation">
  <ul class="list-reset cluster">
    <li class="flyout-trigger">
      <button class="btn ghost"
              type="button"
              aria-expanded="false"
              aria-controls="products-panel">
        Products
        <i class="ti ti-chevron-down" aria-hidden="true"></i>
      </button>

      <div class="flyout"
           id="products-panel"
           aria-label="Products"
           data-flyout-mobile="auto"
           hidden>
        <section aria-labelledby="products-design">
          <h3 id="products-design">Design</h3>
          <ul>
            <li><a href="/figma">Figma integration</a></li>
            <li><a href="/tokens">Design tokens</a></li>
          </ul>
        </section>

        <section aria-labelledby="products-dev">
          <h3 id="products-dev">Development</h3>
          <ul>
            <li><a href="/components">Components</a></li>
            <li><a href="/api">API</a></li>
          </ul>
        </section>

        <footer>
          <a href="/pricing" class="btn primary">See pricing</a>
        </footer>
      </div>
    </li>

    <li><a href="/about" class="btn ghost">About</a></li>
    <li><a href="/contact" class="btn ghost">Contact</a></li>
  </ul>
</nav>

Mega menu

Use class="flyout grid-3" when a nav panel needs multiple link groups. Keep links regular anchors and let the panel collapse to a one-column sheet on mobile with data-flyout-mobile="auto".

<nav aria-label="Product navigation">
  <ul class="list-reset cluster">
    <li class="flyout-trigger">
      <button class="btn ghost"
              type="button"
              aria-expanded="false"
              aria-controls="product-mega-menu">
        Platform
        <i class="ti ti-chevron-down" aria-hidden="true"></i>
      </button>

      <div class="flyout grid-3"
           id="product-mega-menu"
           aria-label="Platform"
           data-flyout-mobile="auto"
           style="--flyout-inline-size: 42rem; --flyout-max-inline-size: 42rem"
           hidden>
        <section aria-labelledby="mega-design">
          <h3 id="mega-design">Design</h3>
          <ul>
            <li><a href="/figma">Figma integration</a></li>
            <li><a href="/tokens">Design tokens</a></li>
            <li><a href="/handoff">Developer handoff</a></li>
          </ul>
        </section>

        <section aria-labelledby="mega-develop">
          <h3 id="mega-develop">Development</h3>
          <ul>
            <li><a href="/components">Components</a></li>
            <li><a href="/api">API</a></li>
            <li><a href="/changelog">Changelog</a></li>
          </ul>
        </section>

        <section aria-labelledby="mega-operate">
          <h3 id="mega-operate">Operate</h3>
          <ul>
            <li><a href="/analytics">Analytics</a></li>
            <li><a href="/security">Security</a></li>
            <li><a href="/support">Support</a></li>
          </ul>
        </section>

        <footer class="cluster"
                style="grid-column: 1 / -1; --cluster-justify: flex-end; border-block-start: var(--border-width) solid var(--border)">
          <a href="/pricing" class="btn primary">See pricing</a>
        </footer>
      </div>
    </li>
  </ul>
</nav>

Context menu

Put data-context-menu on the smallest unit the actions operate on: a file row, card, or canvas item. Several units may reference the same <menu class="flyout">. A normal flyout trigger inside that unit opens the same menu through the same context-aware path. Before opening, the context element dispatches the cancelable actual:context-menu event. Its detail contains the shared menu, the owning context, the exact origin, and the opening trigger (pointer, touch, keyboard, or button). Use it to tailor the static menu to the selected item, or cancel the event to keep it closed. contextFor(menu) from actual-css/js/context-menu returns that detail while handling a menu action. Add data-context-menu-scope only when the flyout should stay inside a specific region. Empty or self constrains to the target itself, parent constrains to the parent, and a selector constrains to the closest matching ancestor or first matching element. The scope also constrains the available height, so omit it when the menu is allowed to escape the card or list item. Long press is opt-in with data-context-menu-long-press. Empty uses the default delay; a number sets the delay in milliseconds.

File.pdf

Right click a detail below, press the context-menu key, or use More.

File.pdf Design team
No context selected.
<div class="card stack"
     id="file-card"
     data-context-menu="file-actions"
     data-context-menu-long-press
     tabindex="0"
     style="min-block-size: 12rem;">
  <div class="cluster justify-content-space-between items-center">
    <strong>File.pdf</strong>
    <button class="btn ghost"
            type="button"
            aria-expanded="false"
            aria-controls="file-actions"
            id="file-actions-trigger">
      More
    </button>
  </div>
  <p class="text-sm text-muted">Right click a detail below, press the context-menu key, or use More.</p>
  <div class="cluster">
    <span class="badge" data-context-item="File name">File.pdf</span>
    <span class="badge" data-context-item="Owner">Design team</span>
  </div>
  <output id="context-result" class="text-sm text-muted" aria-live="polite">No context selected.</output>

  <menu class="flyout"
        id="file-actions"
        aria-labelledby="file-actions-trigger"
        hidden>
    <li><button type="button">Open</button></li>
    <li><button type="button">Rename</button></li>
    <li><hr /></li>
    <li><button class="danger" type="button">Delete</button></li>
  </menu>
</div>

<script>
  const card = document.getElementById("file-card");
  const result = document.getElementById("context-result");

  card.addEventListener("actual:context-menu", (event) => {
    const item = event.detail.origin.closest?.("[data-context-item]");
    const label = item?.dataset.contextItem ?? "the card";
    result.textContent = `Menu opened for ${label} (${event.detail.trigger}).`;
  });
</script>