Chevron left

Components

Dropdowns

Theme

Themes/hound/logo.svg

Hound

Framework

Tailwind CSS logo

Tailwind CSS

Usage guide Dropdown usage guide

Tutorial

The short-form tutorial below represents how to pair Rails UI, Stimulus.js, and Ruby on Rails together to achieve a dropdown pattern using a set of elements working together.

Dependencies

To add transition effects to components like dropdowns, we prefer an additional dependency called stimulus-use.

If for some reason stimulus-use is not installed (check your package.json file.) run the following.

yarn add stimulus-use

Stimulus.js and stimulus-use are already installed as a dependency of the Hound theme.


1. Initializing a dropdown

Use the data-controller="dropdown" attribute to initialize a new dropdown component using Stimulus.js.

<div data-controller="dropdown">
  <button>Dropdown</button>
  <div>
    <a href="#">Menu list item 1</a>
    <a href="#">Menu list item 2</a>
  </div>
</div>
yarn add stimulus-use
// app/javascript/controllers/dropdown_controller.js
import { Controller } from '@hotwired/stimulus'
import { useTransition } from 'stimulus-use'

export default class extends Controller {
  static targets = ['menu', 'trigger']

  connect() {
    useTransition(this, {
      element: this.menuTarget,
    })
  }

  toggle() {
    this.toggleTransition()
  }

  hide(event) {
    if (
      !this.element.contains(event.target) &&
      !this.menuTarget.classList.contains('hidden')
    ) {
      this.leave()
    }
  }
}

Be sure the controller is registered inside app/javascript/controllers/index.js. (This should already be the case when you installed the Hound theme.)

// app/javascript/controllers/index.js

import { application } from './application'

import DropdownController from './dropdown_controller.js'
application.register('dropdown', DropdownController)

2. Triggering a dropdown

To trigger a dropdown to appear you will need some form of actionable target like a button or a element. Append the following attributes to your target. data-action="dropdown#toggle click@window->dropdown#hide" (see the base example below for a complete concept).

<div data-controller="dropdown">
  <button data-action="click->dropdown#toggle click@window->dropdown#hide">Dropdown</button>
  <div>
    <a href="#">Menu list item 1</a>
    <a href="#">Menu list item 2</a>
  </div>
</div>
// app/javascript/controllers/index.js

import { application } from './application'

import DropdownController from './dropdown_controller.js'
application.register('dropdown', DropdownController)

3. Defining the menu

Each dropdown has a corresponding menu that gets hidden by default. The menu requires the following attribute to function data-dropdown-target="menu". The element the target is appended to also requires the class name class='hidden' to be present.

<div data-controller="dropdown">
  <button data-action="click->dropdown#toggle click@window->dropdown#hide">Dropdown</button>
  <div class="hidden" data-dropdown-target="menu">
    <a href="#">Menu list item 1</a>
    <a href="#">Menu list item 2</a>
  </div>
</div>
// app/javascript/controllers/index.js

import { application } from './application'

import DropdownController from './dropdown_controller.js'
application.register('dropdown', DropdownController)

4. Adding transitions and effects

Stimulus.js doesn't provide help in the context of animation and transitions so we reached for stimulus-use to help.

Using the library you can leverage data attributes to add specific effects provided by Tailwind CSS classes at different states of a dropdown transition.

<div data-controller="dropdown">
  <button data-action="click->dropdown#toggle click@window->dropdown#hide">Dropdown</button>
  <div
    class="hidden"
    data-dropdown-target="menu"
    data-transition-enter-from="opacity-0 scale-95"
    data-transition-enter-to="opacity-100 scale-100"
    data-transition-leave-from="opacity-100 scale-100"
    data-transition-leave-to="opacity-0 scale-95">
    <a href="#">Menu list item 1</a>
    <a href="#">Menu list item 2</a>
  </div>
</div>

Dropdowns

<div data-controller="dropdown" class="relative inline-block">
  <button type="button" data-action="click->dropdown#toggle click@window->dropdown#hide" class="btn btn-primary pr-3">
    Dropdown
    <%= icon "chevron-down", classes: "w-3 h-3 ml-2" %>
  </button>
  <div
    class="hidden transition transform origin-top-left absolute left-0 top-10 bg-white rounded-lg shadow-xl shadow-slate-900/10 border border-slate-200 md:w-[200px] w-full z-50 py-2 dark:bg-slate-700 dark:shadow-slate-900/50 dark:border-slate-500/60 md:text-sm text-base font-medium text-slate-600 dark:text-slate-200"
    data-dropdown-target="menu"
    data-transition-enter-from="opacity-0 scale-95"
    data-transition-enter-to="opacity-100 scale-100"
    data-transition-leave-from="opacity-100 scale-100"
    data-transition-leave-to="opacity-0 scale-95"
    >
    <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300">Bookmark</a>
    <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300">Report</a>
    <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300">Export</a>
  </div>
</div>
<div data-controller="dropdown" class="relative inline-block">
  <%= button_tag data: { action: "click->dropdown#toggle click@window->dropdown#hide" }, class: "btn btn-primary" do %>
    Dropdown
    <%= icon "chevron-down", classes: "w-3 h-3 ml-2" %>
  <% end %>
  <div
    class="hidden transition transform origin-top-left absolute left-0 top-10 bg-white rounded-lg shadow-xl shadow-slate-900/10 border border-slate-200 md:w-[200px] w-full z-50 py-2 dark:bg-slate-700 dark:shadow-slate-900/50 dark:border-slate-500/60 md:text-sm text-base font-medium text-slate-600 dark:text-slate-200"
    data-dropdown-target="menu"
    data-transition-enter-from="opacity-0 scale-95"
    data-transition-enter-to="opacity-100 scale-100"
    data-transition-leave-from="opacity-100 scale-100"
    data-transition-leave-to="opacity-0 scale-95"
    >
    <%= link_to "Bookmark", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300" %>
    <%= link_to "Report", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300" %>
    <%= link_to "Export", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300" %>
  </div>
</div>
.relative.inline-block{"data-controller" => "dropdown"}
  = button_tag data: { action: "click->dropdown#toggle click@window->dropdown#hide" }, class: "btn btn-primary" do
    Dropdown
    \#{icon "chevron-down", classes: "w-3 h-3 ml-2"}
  .hidden.transition.transform.origin-top-left.absolute.left-0.top-10.bg-white.rounded-lg.shadow-xl.border.border-slate-200.w-full.z-50.py-2.dark:bg-slate-700.md:text-sm.text-base.font-medium.text-slate-600.dark:text-slate-200{class: "shadow-slate-900/10 md:w-[200px] dark:shadow-slate-900/50 dark:border-slate-500/60", "data-dropdown-target" => "menu", "data-transition-enter-from" => "opacity-0 scale-95", "data-transition-enter-to" => "opacity-100 scale-100", "data-transition-leave-from" => "opacity-100 scale-100", "data-transition-leave-to" => "opacity-0 scale-95"}
    = link_to "Bookmark", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300"
    = link_to "Report", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300"
    = link_to "Export", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300"

Dropdowns

<div data-controller="dropdown" class="relative inline-block">
  <button type="button" data-action="click->dropdown#toggle click@window->dropdown#hide" class="btn btn-primary pr-3">
    Dropdown
    <%= icon "chevron-down", classes: "w-3 h-3 ml-2" %>
  </button>
  <div
    class="hidden transition transform origin-to-left absolute left-0 top-10 bg-white rounded-lg shadow-xl shadow-slate-900/10 border border-slate-200 md:w-[200px] w-full z-50 py-2 dark:bg-slate-700 dark:shadow-slate-900/50 dark:border-slate-500/60 md:text-sm text-base font-medium text-slate-600 dark:text-slate-200"
    data-dropdown-target="menu"
    data-transition-enter-from="opacity-0 scale-95"
    data-transition-enter-to="opacity-100 scale-100"
    data-transition-leave-from="opacity-100 scale-100"
    data-transition-leave-to="opacity-0 scale-95">
      <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 dark:hover:text-indigo-300 group flex items-center justify-start space-x-3">
        <%= icon "bookmark", classes: "w-4 h-4 text-slate-600 group-hover:text-indigo-600 flex-shrink-0" %>
        <span>Bookmark</span>
      </a>
      <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 dark:hover:text-indigo-300 group flex items-center justify-start space-x-3">
        <%= icon "flag", classes: "w-4 h-4 text-slate-600 group-hover:text-indigo-600 flex-shrink-0" %>
        <span>Report</span>
      </a>
      <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 dark:hover:text-indigo-300 group flex items-center justify-start space-x-3">
        <%= icon "arrow-down-tray", classes: "w-4 h-4 text-slate-600 group-hover:text-indigo-600 flex-shrink-0" %>
        <span>Export</span>
      </a>
  </div>
</div>
<div data-controller="dropdown" class="relative inline-block">
   <%= button_tag data: { action: "click->dropdown#toggle click@window->dropdown#hide" }, class: "btn btn-primary" do %>
    Dropdown
    <%= icon "chevron-down", classes: "w-3 h-3 ml-2" %>
  <% end %>
  <div
    class="hidden transition transform origin-to-left absolute left-0 top-10 bg-white rounded-lg shadow-xl shadow-slate-900/10 border border-slate-200 md:w-[200px] w-full z-50 py-2 dark:bg-slate-700 dark:shadow-slate-900/50 dark:border-slate-500/60 md:text-sm text-base font-medium text-slate-600 dark:text-slate-200"
    data-dropdown-target="menu"
    data-transition-enter-from="opacity-0 scale-95"
    data-transition-enter-to="opacity-100 scale-100"
    data-transition-leave-from="opacity-100 scale-100"
    data-transition-leave-to="opacity-0 scale-95">
    <%= link_to, class: "px-4 py-[.4rem] hover:text-indigo-600 dark:hover:text-indigo-300 group flex items-center justify-start space-x-3" do %>
      <%= icon "bookmark", classes: "w-4 h-4 text-slate-600 group-hover:text-indigo-600 flex-shrink-0" %>
      <span>Bookmark</span>
    <% end %>
    <%= link_to, class: "px-4 py-[.4rem] hover:text-indigo-600 dark:hover:text-indigo-300 group flex items-center justify-start space-x-3" do %>
      <%= icon "flag", classes: "w-4 h-4 text-slate-600 group-hover:text-indigo-600 flex-shrink-0" %>
      <span>Report</span>
    <% end %>
    <%= link_to, class: "px-4 py-[.4rem] hover:text-indigo-600 dark:hover:text-indigo-300 group flex items-center justify-start space-x-3" do %>
      <%= icon "arrow-down-tray", classes: "w-4 h-4 text-slate-600 group-hover:text-indigo-600 flex-shrink-0" %>
      <span>Export</span>
    <% end %>
  </div>
</div>
.relative.inline-block{"data-controller" => "dropdown"}
  = button_tag data: { action: "click->dropdown#toggle click@window->dropdown#hide" }, class: "btn btn-primary" do
    Dropdown
    \#{icon "chevron-down", classes: "w-3 h-3 ml-2"}
  .hidden.transition.transform.origin-to-left.absolute.-left-1.top-10.bg-white.rounded-lg.shadow-xl.border.border-slate-200.z-50.py-2.dark:bg-slate-700.text-sm.font-medium.text-slate-600.dark:text-slate-200{class: "shadow-slate-900/10 min-w-[200px] dark:shadow-slate-900/50 dark:border-slate-500/60", "data-dropdown-target" => "menu", "data-transition-enter-from" => "opacity-0 scale-95", "data-transition-enter-to" => "opacity-100 scale-100", "data-transition-leave-from" => "opacity-100 scale-100", "data-transition-leave-to" => "opacity-0 scale-95"}
    = link_to, class: "px-4 py-[.4rem] hover:text-indigo-600 dark:hover:text-indigo-300 group flex items-center justify-start space-x-3" do
    = icon "bookmark", classes: "w-4 h-4 text-slate-600 group-hover:text-indigo-600 flex-shrink-0"
    %span Bookmark
    = link_to, class: "px-4 py-[.4rem] hover:text-indigo-600 dark:hover:text-indigo-300 group flex items-center justify-start space-x-3" do
    = icon "flag", classes: "w-4 h-4 text-slate-600 group-hover:text-indigo-600 flex-shrink-0"
    %span Report
    = link_to, class: "px-4 py-[.4rem] hover:text-indigo-600 dark:hover:text-indigo-300 group flex items-center justify-start space-x-3" do
    = icon "arrow-down-tray", classes: "w-4 h-4 text-slate-600 group-hover:text-indigo-600 flex-shrink-0"
    %span Export

Dropdowns

Divide items in a list using a divide-y class from Tailwind CSS.

Depending on how your dropdown list data originates, you may need to get creative on assigning padding between the first and last items in the dropdown menu.

By default each item has a padding offset of py-1 applied. You may find that you will need to override this for the first and last items to produce an even gap across all menu list items while displaying dividers.

<div data-controller="dropdown" class="relative inline-block">
  <button type="button" data-action="click->dropdown#toggle click@window->dropdown#hide" class="btn btn-white pr-3">
    Dropdown with dividers
    <%= icon "chevron-down", classes: "w-3 h-3 ml-2" %>
  </button>
  <div
    class="hidden transition transform origin-top-left absolute left-0 top-10 bg-white rounded-lg shadow-xl shadow-slate-900/10 border border-slate-200 md:w-[200px] w-full z-50 py-2 dark:bg-slate-700 dark:shadow-slate-900/50 dark:border-slate-500/60 md:text-sm text-base font-medium text-slate-600 dark:text-slate-200 divide-y dark:divide-slate-500/75"
    data-dropdown-target="menu"
    data-transition-enter-from="opacity-0 scale-95"
    data-transition-enter-to="opacity-100 scale-100"
    data-transition-leave-from="opacity-100 scale-100"
    data-transition-leave-to="opacity-0 scale-95"
    >
    <div role="none" class="pb-1">
      <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 block">Edit</a>
    </div>
    <div role="none" class="py-1">
      <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 block">Share</a>
      <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 block">Export</a>
    </div>
    <div role="none" class="pt-1">
      <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 block">Delete</a>
    </div>
  </div>
</div>
<div data-controller="dropdown" class="relative inline-block">
  <%= button_tag data: { action: "click->dropdown#toggle click@window->dropdown#hide" }, class: "btn btn-primary" do %>
    Dropdown with dividers
    <%= icon "chevron-down", classes: "w-3 h-3 ml-2" %>
  <% end %>
  <div
    class="hidden transition transform origin-top-left absolute left-0 top-10 bg-white rounded-lg shadow-xl shadow-slate-900/10 border border-slate-200 md:w-[200px] w-full z-50 py-2 dark:bg-slate-700 dark:shadow-slate-900/50 dark:border-slate-500/60 md:text-sm text-base font-medium text-slate-600 dark:text-slate-200 divide-y dark:divide-slate-500/75"
    data-dropdown-target="menu"
    data-transition-enter-from="opacity-0 scale-95"
    data-transition-enter-to="opacity-100 scale-100"
    data-transition-leave-from="opacity-100 scale-100"
    data-transition-leave-to="opacity-0 scale-95"
    >
    <div role="none" class="pb-1">
      <%= link_to "Edit", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block" %>
    </div>
    <div role="none" class="py-1">
      <%= link_to "Share", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block" %>
      <%= link_to "Export", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block" %>
    </div>
    <div role="none" class="pt-1">
      <%= link_to "Delete", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block" %>
    </div>
  </div>
</div>
.relative.inline-block{"data-controller" => "dropdown"}
  = button_tag data: { action: "click->dropdown#toggle click@window->dropdown#hide" }, class: "btn btn-primary" do
    Dropdown with dividers
    #{icon "chevron-down", classes: "w-3 h-3 ml-2"}
  .hidden.transition.transform.origin-top-left.absolute.left-0.top-10.bg-white.rounded-lg.shadow-xl.border.border-slate-200.w-full.z-50.py-2.dark:bg-slate-700.md:text-sm.text-base.font-medium.text-slate-600.dark:text-slate-200.divide-y{class: "shadow-slate-900/10 md:w-[200px] dark:shadow-slate-900/50 dark:border-slate-500/60 dark:divide-slate-500/75", "data-dropdown-target" => "menu", "data-transition-enter-from" => "opacity-0 scale-95", "data-transition-enter-to" => "opacity-100 scale-100", "data-transition-leave-from" => "opacity-100 scale-100", "data-transition-leave-to" => "opacity-0 scale-95"}
    .pb-1{role: "none"}
      = link_to "Edit", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block"
    .py-1{role: "none"}
      = link_to "Share", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block"
      = link_to "Export", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block"
    .pt-1{role: "none"}
      = link_to "Delete", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block"

Dropdowns

<div data-controller="dropdown" class="relative inline-block">
  <button type="button" data-action="click->dropdown#toggle click@window->dropdown#hide" class="btn btn-white pr-3">
    Right-aligned Dropdown Menu
    <%= icon "chevron-down", classes: "w-3 h-3 ml-2" %>
  </button>
  <ul
    class="hidden transition transform origin-top-right absolute -left-1 top-10 bg-white rounded-lg shadow-xl shadow-slate-900/10 border border-slate-200 min-w-[200px] z-50 py-2 dark:bg-slate-700 dark:shadow-slate-900/50 dark:border-slate-500/60 text-sm font-medium text-slate-600 dark:text-slate-200"
    data-dropdown-target="menu"
    data-transition-enter-from="opacity-0 scale-95"
    data-transition-enter-to="opacity-100 scale-100"
    data-transition-leave-from="opacity-100 scale-100"
    data-transition-leave-to="opacity-0 scale-95"
    >
    <li>
      <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300">Bookmark</a>
    </li>
    <li>
      <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300">Report</a>
    </li>
    <li>
      <a href="#" class="px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300">Export</a>
    </li>
  </ul>
</div>
<div data-controller="dropdown" class="relative inline-block">
    <%= button_tag data: { action: "click->dropdown#toggle click@window->dropdown#hide" }, class: "btn btn-primary" do %>
    Right-aligned Dropdown Menu
    <%= icon "chevron-down", classes: "w-3 h-3 ml-2" %>
  <% end %>
  <div
    class="hidden transition transform origin-top-right absolute -left-1 top-10 bg-white rounded-lg shadow-xl shadow-slate-900/10 border border-slate-200 min-w-[200px] z-50 py-2 dark:bg-slate-700 dark:shadow-slate-900/50 dark:border-slate-500/60 text-sm font-medium text-slate-600 dark:text-slate-200"
    data-dropdown-target="menu"
    data-transition-enter-from="opacity-0 scale-95"
    data-transition-enter-to="opacity-100 scale-100"
    data-transition-leave-from="opacity-100 scale-100"
    data-transition-leave-to="opacity-0 scale-95"
    >
    <div>
      <%= link_to "Bookmark", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300" %>
    </div>
    <div>
      <%= link_to "Report", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300" %>
    </div>
    <div>
      <%= link_to "Export", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300" %>
    </div>
  </div>
</div>
.relative.inline-block{"data-controller" => "dropdown"}
  = button_tag data: { action: "click->dropdown#toggle click@window->dropdown#hide" }, class: "btn btn-primary"
    Right-aligned Dropdown Menu
    = icon "chevron-down", classes: "w-3 h-3 ml-2"
  .hidden.transition.transform.origin-top-right.absolute.-left-1.top-10.bg-white.rounded-lg.shadow-xl.border.border-slate-200.z-50.py-2.dark:bg-slate-700.text-sm.font-medium.text-slate-600.dark:text-slate-200{class: "shadow-slate-900/10 min-w-[200px] dark:shadow-slate-900/50 dark:border-slate-500/60", "data-dropdown-target" => "menu", "data-transition-enter-from" => "opacity-0 scale-95", "data-transition-enter-to" => "opacity-100 scale-100", "data-transition-leave-from" => "opacity-100 scale-100", "data-transition-leave-to" => "opacity-0 scale-95"}
    %div
      = link_to "Bookmark", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300"
    %div
      = link_to "Report", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300"
    %div
      = link_to "Export", "#", class: "px-4 py-[.4rem] hover:text-indigo-600 block dark:hover:text-indigo-300"