VSN.jsGitHub ↗

COOKBOOK / LIVE EXAMPLE

Dropdown Menu example with VSN.js

Real markup. Real VSN. Try it below.

← All examples · Open full screen ↗

View example source
<section id="dropdown-example" class="example-card cookbook-example">
  <header class="cookbook-heading">
    <div>
      <p class="cookbook-eyebrow">cookbook</p>
      <h1>Dropdown menu</h1>
      <p class="cookbook-lede">
        A nested uses <code>!outside</code> and keyboard flags to manage a menu.
      </p>
    </div>
    <span class="cookbook-badge">outside + keys</span>
  </header>

  <div class="cookbook-stage">
    <div class="cookbook-dropdown">
      <button
        class="cookbook-dropdown-toggle"
        type="button"
        aria-haspopup="listbox"
        aria-expanded="false"
      >
        <span vsn-bind:from="selected">Open</span>
        <span aria-hidden="true">⌄</span>
      </button>

      <div class="cookbook-dropdown-menu" role="listbox" vsn-show="open">
        <button class="cookbook-dropdown-option" type="button" data-value="Open">Open</button>
        <button class="cookbook-dropdown-option" type="button" data-value="In progress">In progress</button>
        <button class="cookbook-dropdown-option" type="button" data-value="Blocked">Blocked</button>
        <button class="cookbook-dropdown-option" type="button" data-value="Done">Done</button>
      </div>
    </div>

    <button class="btn secondary" type="button">Click outside the menu</button>
  </div>

  <p class="cookbook-note">
    Select an option, press Escape, or click outside the dropdown. The selected value is shared through the root scope.
  </p>
</section>

<script type="text/vsn">
  #dropdown-example {
    open: false;
    selected: "Open";

    .cookbook-dropdown {
      on click!outside() {
        root.open = false;
      }
    }

    .cookbook-dropdown-toggle {
      @aria-expanded :< root.open;

      on click() {
        root.open = !root.open;
      }

      on keyup!escape() {
        root.open = false;
      }
    }
    .cookbook-dropdown-option {
      @data-value :> value;
      @aria-selected :< (root.selected == value);

      on click() {
        root.selected = value;
        root.open = false;
      }
    }
  }
</script>