Components
Dropdown
.tf-dropdown is the click/hover disclosure primitive: a trigger and a positioned panel. It doesn't require the panel to contain a menu — when it does, pair it with Menu for roving-focus keyboard navigation among the items.
How It Works
A .tf-dropdown wraps a [data-tf-dropdown-trigger] button and a .tf-dropdown-menu panel. Clicking the trigger toggles the panel; Escape and an outside click close it.
Click vs. hover
Default is click-to-open. Add data-tf-dropdown-hover to the .tf-dropdown wrapper to also open on pointer hover and focus — click still works alongside it.
Alignment
The panel aligns to the trigger's start edge by default. Add data-align="end" on .tf-dropdown-menu to align it to the end edge instead — useful near the right side of a header.
Not just menus
The panel can hold anything — a small form, a color swatch grid, plain text. Only add role="menu" and .tf-menu when the content really is a list of commands.
Keyboard model
Dropdown itself only handles opening, closing, and Escape. Arrow-key navigation between items comes from nesting a Menu inside the panel — a dropdown holding a form uses ordinary Tab order instead.
Live Examples
Click-triggered menu dropdown
The default mode, holding a .tf-menu for keyboard navigation.
<div class="tf-dropdown">
<button type="button" class="tf-btn" data-tf-dropdown-trigger>Actions <i class="ti ti-005063" aria-hidden="true"></i></button>
<div class="tf-dropdown-menu tf-menu" role="menu">
<button type="button" class="tf-dropdown-item tf-menu-item" role="menuitem">Rename</button>
<button type="button" class="tf-dropdown-item tf-menu-item" role="menuitem">Duplicate</button>
<button type="button" class="tf-dropdown-item tf-menu-item" role="menuitem" disabled>Archive</button>
</div>
</div>Hover-triggered, end-aligned
data-tf-dropdown-hover plus data-align="end" — the pattern this site's own header "More" menu uses.
<div class="tf-dropdown" data-tf-dropdown-hover>
<button type="button" class="tf-btn tf-btn-ghost" data-tf-dropdown-trigger>Settings</button>
<div class="tf-dropdown-menu tf-menu" role="menu" data-align="end">
<button type="button" class="tf-dropdown-item tf-menu-item" role="menuitem">Profile</button>
<button type="button" class="tf-dropdown-item tf-menu-item" role="menuitem">Preferences</button>
</div>
</div>Non-menu content (a form)
No .tf-menu/role="menu" — ordinary Tab order applies inside, since this isn't a list of commands.
<div class="tf-dropdown">
<button type="button" class="tf-btn" data-tf-dropdown-trigger>Filter</button>
<div class="tf-dropdown-menu tf-w-64">
<label class="tf-label" for="dropdown-filter-input">Search term</label>
<input id="dropdown-filter-input" class="tf-input" type="text" placeholder="Filter by name">
<button type="button" class="tf-btn tf-btn-primary tf-btn-sm tf-w-full">Apply filter</button>
</div>
</div>States
| State | How it's represented |
|---|---|
| Closed | hidden attribute and data-state="closed" on the panel; aria-expanded="false" on the trigger. |
| Open | hidden removed, data-state="open", aria-expanded="true". |
| Disabled menu item | Native disabled attribute — skipped by Menu's roving focus if the panel uses .tf-menu, as shown in the first example. |
| Hover mode, pointer leaves | Closes after a short delay rather than instantly, so moving the pointer from trigger to panel doesn't close it mid-transit. |
Accessibility
| Requirement | TicFrame guidance |
|---|---|
| Relationship | TicFrame sets aria-haspopup="menu" and tracks aria-expanded on the trigger automatically. |
| Menu content | Add role="menu" and .tf-menu when the panel is a command list — see Menu for item roles and keyboard behavior. |
| Non-menu content | Leave off role="menu"; form controls inside use their own native semantics and ordinary Tab order. |
| Keyboard | Escape closes the panel and returns focus is left where it was (unlike Modal/Offcanvas, Dropdown doesn't force focus into the panel on open). |
API Reference
Classes
| Class | Purpose |
|---|---|
tf-dropdown | Required wrapper around the trigger and panel. |
tf-dropdown-menu | Required panel. |
tf-dropdown-item | Reset styling for a <button> or <a> row inside the panel. |
Attributes
| Attribute | Where | Description |
|---|---|---|
data-tf-dropdown-trigger | Trigger button | Marks the click target. |
data-tf-dropdown-hover | .tf-dropdown wrapper | Also opens on hover/focus, in addition to click. |
data-align="end" | .tf-dropdown-menu | Aligns the panel to the trigger's end edge instead of its start edge. |
Events
| Event | When it fires | Target |
|---|---|---|
tf:dropdown:open | After the panel opens. | .tf-dropdown wrapper |
tf:dropdown:close | After the panel closes. | .tf-dropdown wrapper |
Troubleshooting
| Symptom | Fix |
|---|---|
| Panel is clipped inside a card or scroll container | Don't nest a dropdown inside an element with overflow: hidden unless that element specifically rounds child corners — see the Menu page's note on .tf-doc-example for the equivalent site-wide fix. |
| Arrow keys don't move between items | The panel needs .tf-menu and item elements need .tf-menu-item — plain .tf-dropdown-item alone only gets click/Enter activation, not roving focus. |
| Hover mode closes while moving the mouse into the panel | This shouldn't happen — the close is deliberately delayed. If it does, check for another script calling TicFrame.init() a second time on the same element, which can double-register listeners. |
| Panel opens off the edge of the screen | Add data-align="end" near the right edge of the viewport (or left edge in RTL). |