Dropdown

Form-like element that allows users to select an option from a list of choices.

Class name Type
.prs-dropdown Component Container
.prs-menu Component For <ul>
.prs-menu-item Part For "button" container
.prs-menu-item-label Part For label within "button"
.prs-menu-icon Part For icon wrapper within "button"
  • Default

    This is an Alpine.js example. In the code block you can see the reactive data and the attributes that need to be toggled based on that data.

    <div
      x-data="{
        items: ['Assign', 'Edit', 'Delete'],
        selected: null,
        open: false,
        toggle() {
          if (this.open) { return this.close() }
          this.$refs.button.focus()
          this.open = true
        },
        close(focusAfter) {
          if (!this.open) return
          this.open = false
          focusAfter && focusAfter.focus()
        }
      }"
      x-id="['dropdown']"
      @focusin.window="!$refs.panel.contains($event.target) && close()"
      @keydown.escape.prevent.stop="close($refs.button)"
      @keydown.up.prevent.stop="$focus.prev()"
      @keydown.down.prevent.stop="$focus.next()"
      class="prs-dropdown"
    >
      <button
        x-ref="button"
        @click="toggle()"
        class="prs-btn prs-btn-secondary"
        :aria-expanded="open"
        :aria-controls="$id('dropdown')"
        aria-label="Some dropdown"
      >
        <span x-text="selected !== null ? items[selected] : 'Dropdown'"></span>
      </button>
      <ul
        x-ref="panel"
        x-show="open"
        x-anchor.bottom-start.offset.4="$refs.button"
        x-transition
        @click.outside="close($refs.button)"
        :id="$id('dropdown')"
        :class="open && 'prs-menu_open'"
        class="prs-menu"
      >
        <template x-for="(item, index) in items" hidden> <!-- <- this is just for demo purposes -->
          <li>
            <button
              @click.prevent="selected === index ? selected = null : selected = index; close()"
              class="prs-menu-item"
            >
              <span class="prs-menu-item-label" x-text="item"></span>
              <span class="prs-menu-icon" x-show="selected === index" aria-label="Selected">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" role="img"><use href="/_assets/prs-icons.svg#status-check" /></svg>
              </span>
            </button>
          </li>
        </template>
      </ul>
    </div>
    
  • <ul class="prs-menu">
      <li><button class="prs-menu-item"><span>Menu item</span></button></li>
      <li>
        <button class="prs-menu-item">
          <span class="prs-menu-item-label" aria-label="Truncate very long menu item labels" title="Truncate very long menu item labels">Truncate very long menu item labels</span>
          <!-- active/selected icon -->
          <span class="prs-menu-icon" aria-label="Selected">
            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" role="img"><use href="/_assets/prs-icons.svg#status-check" /></svg>
          </span>
        </button>
      </li>
      <li><button class="prs-menu-item"><span>Menu item</span></button></li>
    </ul>
    

CSS

Full Library

Component Only

.prs-dropdown {
  position: relative;

  > .prs-btn {
    padding-right: 2rem;
    background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                      linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-size: 4px 4px, 4px 4px;
    background-position: calc(100% - 20px) calc(1px + 50%), calc(100% - 16.1px) calc(1px + 50%);
    background-repeat: no-repeat;
  }
}

.prs-menu {
  border: 1px solid var(--prs-c-gray-300);
  border-bottom: 0 none;
  width: 100%;
  min-width: 12.5rem;
  max-width: 16rem;
  background-color: var(--prs-c-white);
  display: flex;
  flex-direction: column;
  box-shadow: 1px 3px 4px rgb(0 0 0 / 0.2);

  > li {
    border-bottom: 1px solid var(--prs-c-gray-300);
  }
}

.prs-menu_open {
  z-index: 1;
}

.prs-menu-item {
  padding: 0.5rem 1rem;
  width: 100%;
  max-width: 18.75rem;
  color: var(--prs-c-primary);
  font-size: 1rem;
  line-height: 1.5rem;
  text-align: start;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  border-radius: 0;
  cursor: pointer;
  transition-property: var(--prs-transition-property);
  transition-timing-function: var(--prs-transition-timing);
  transition-duration: var(--prs-transition-duration);

  &:hover {
    background-color: var(--prs-c-primary);
    color: var(--prs-c-white);
  }

  &:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 2px currentColor;
  }

  .prs-menu-icon {
    flex-shrink: 0;
  }
}

.prs-menu-item-label {
  flex-grow: 1;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

Coming soon...