Dropdown
Overview
The Dropdown component provides Bulma's versatile dropdown menu for your Bulma React UI.
It supports custom triggers, menu alignment, hover or click activation, right/up direction, disabled state, controlled/uncontrolled open state, menu dividers, and full Bulma/utility helper props. Use it for navigation menus, actions, or custom pop-up lists.
Dropdowns can be fully controlled, used as hoverable, or triggered by click. Menu items and dividers are included.
Import
import {
Dropdown,
Button,
Field,
Control,
Span,
} from '@allxsmith/bestax-bulma';
Usage
Default Dropdown
To create a dropdown menu, use the Dropdown component with a label for the trigger and Dropdown.Item children for each menu option. You can add a Dropdown.Divider to separate groups of items. This pattern is ideal for navigation menus, action lists, or custom pop-up menus in your UI.
import { Dropdown } from '@allxsmith/bestax-bulma'; function Example() { return ( <Dropdown label="Dropdown Menu"> <Dropdown.Item>First Item</Dropdown.Item> <Dropdown.Item>Second Item</Dropdown.Item> <Dropdown.Divider /> <Dropdown.Item>Third Item</Dropdown.Item> <Dropdown.Item>Fourth Item</Dropdown.Item> <Dropdown.Item>Fifth Item</Dropdown.Item> </Dropdown> ); }
Custom Tags (button, div, anchor)
Use the as prop on Dropdown.Item to render different HTML elements, such as a, div, or button. This allows you to customize the behavior and semantics of each dropdown item, supporting links, actions, or custom content.
<Dropdown label="Custom Dropdown Content"> <Dropdown.Item as="a" href="https://example.com" target="_blank"> Anchor Item </Dropdown.Item> <Dropdown.Item as="div">Div Item</Dropdown.Item> <Dropdown.Item as="button" onClick={() => alert('Clicked!')}> Button Item </Dropdown.Item> </Dropdown>
Hoverable and Always Active
Add the hoverable prop to open the dropdown on hover, and the active prop to keep it always open. This is useful for menus that should remain visible or for previewing dropdown content without a click.
<Dropdown label="Hoverable + Active" hoverable active> <Dropdown.Item>Hover or Always Open</Dropdown.Item> <Dropdown.Item>Second</Dropdown.Item> <Dropdown.Divider /> <Dropdown.Item>Another</Dropdown.Item> </Dropdown>
Right-Aligned Dropdown
Set the right prop to align the dropdown menu to the right edge of its trigger. This is useful for menus in toolbars or when space is limited on the left.
<Dropdown label="Dropdown Right" right> <Dropdown.Item>Right 1</Dropdown.Item> <Dropdown.Item>Right 2</Dropdown.Item> </Dropdown>
Upward Dropdown
Use the up prop to make the dropdown menu open upward instead of downward. This is helpful when the dropdown is near the bottom of the viewport or container.
<Dropdown label="Dropdown Up" up> <Dropdown.Item>Up 1</Dropdown.Item> <Dropdown.Item>Up 2</Dropdown.Item> </Dropdown>
Controlled Dropdown Example
Control the open/close state of the dropdown by setting the active prop and handling state changes with onActiveChange. This pattern is useful for advanced interactions or integrating with other UI state.
function example() { const [open, setOpen] = useState(false); return ( <Dropdown label="Controlled Dropdown" active={open} onActiveChange={setOpen} > <Dropdown.Item>Item A</Dropdown.Item> <Dropdown.Item>Item B</Dropdown.Item> </Dropdown> ); }
Forwarded ref
Dropdown forwards a ref to the root .dropdown element. It is merged with the ref the component uses internally for outside-click and keyboard handling, so attaching your own does not disable either.
function example() { const dropdownRef = React.useRef(null); const [tag, setTag] = React.useState(null); return ( <> <Dropdown label="Pick one" ref={dropdownRef}> <Dropdown.Item>First</Dropdown.Item> <Dropdown.Item>Second</Dropdown.Item> </Dropdown> <Button mt="3" onClick={() => setTag(dropdownRef.current?.className)}> Read the root from its ref </Button> <p>Root class: {tag ?? '—'}</p> </> ); }
Callback refs behave the same on React 18 and 19: a cleanup function you return runs on detach instead of the ref being called with null.
Split Button
A split button pairs a main action with a menu of related ones. Put a Button and a Dropdown in a Field hasAddons, one Control each. Field hasAddons squares the inner corners of the button inside each Control, so it joins the Dropdown's trigger as well. Buttons hasAddons can't: it joins buttons that sit side by side as siblings, and the trigger sits alone inside the Dropdown's own wrapper.
The trigger shows only the Dropdown's arrow, so its label is visually hidden text that names the menu for screen readers. Both halves are in the tab order, and the menu keeps the keyboard support described under Accessibility. Keep the main button in the default color: the trigger takes no color prop, so a colored main button can't match it.
function SplitButton() { const [lastAction, setLastAction] = useState('none yet'); return ( <> <Field hasAddons> <Control> <Button onClick={() => setLastAction('saved')}>Save</Button> </Control> <Control> <Dropdown label={<Span visibility="sr-only">More save options</Span>}> <Dropdown.Item as="button" onClick={() => setLastAction('saved as a draft')} > Save as draft </Dropdown.Item> <Dropdown.Item as="button" onClick={() => setLastAction('saved a copy')} > Save a copy </Dropdown.Item> </Dropdown> </Control> </Field> <p role="status">Last action: {lastAction}</p> </> ); }
Compound (dot-notation) usage
DropdownItem and DropdownDivider are also available as Dropdown.Item and Dropdown.Divider, so the whole menu can be composed from the single Dropdown import.
<Dropdown label="Dropdown Menu" active> <Dropdown.Item>First Item</Dropdown.Item> <Dropdown.Item>Second Item</Dropdown.Item> <Dropdown.Divider /> <Dropdown.Item>Third Item</Dropdown.Item> </Dropdown>
Accessibility
- The dropdown root is a
<div class="dropdown">with ARIA roles/attributes for menu and trigger. - The trigger button uses
aria-haspopup,aria-controls, andaria-expanded. - Menu items are focusable and use
role="menuitem". An item givenrole="menuitemcheckbox"orrole="menuitemradio"stays in the arrow-key order. Dropdown.Item as="button"defaults totype="button", so a menu item inside a form doesn't submit it. Passtype="submit"ortype="reset"and yours is used instead; any other value renderstype="button".- Clicking outside closes the dropdown in most cases.
Dropdown implements the WAI-ARIA Menu Button pattern
out of the box:
- On the trigger: ArrowDown or Enter/Space open the menu and focus the first item; ArrowUp opens the menu and focuses the last item; Escape closes an open menu.
- Inside the menu: ArrowDown/ArrowUp move between items and wrap around; Home/End jump to the first/last item; Escape closes the menu and returns focus to the trigger; Tab closes the menu and lets focus continue naturally.
- On an item: Enter or Space activates it the way a click does, so its
onClickruns and the menu closes unlesscloseOnClickisfalse. That includes the default anchor without anhrefandas="div", which the browser would not activate on its own. - When running an item closes the menu, by click or by key, focus goes back to the trigger,
as it does for Escape. An item whose
onClickmoves focus somewhere else keeps it there. - Disabled items (a
Dropdown.Itemrendered with a nativedisabledattribute oraria-disabled="true") and dividers are skipped during arrow-key navigation.
There is no typeahead and no built-in outside-click handling inside the menu beyond the existing click-outside-to-close behavior — add those yourself if your use case needs them.
Related Components
Button: Use Bulma/Bestax buttons as triggers if needed.Popover: A panel of interactive content, such as a form, rather than a menu of actions.- Helper Props: All Bulma utility helpers can be used.
Additional Resources
You can use all Bulma helper props with <Dropdown />, <Dropdown.Item />, and <Dropdown.Divider /> for utility-based styling.
Props
Dropdown
| Prop | Type | Default | Description |
|---|---|---|---|
label | React.ReactNode | — | The dropdown button/trigger content. |
children | React.ReactNode | — | Dropdown menu items and dividers. |
className | string | — | Additional CSS classes for root. |
menuClassName | string | — | Additional CSS classes for the dropdown menu. |
active | boolean | — | Whether the dropdown is open (controlled). |
up | boolean | false | Dropdown menu opens upward. |
right | boolean | false | Menu is right-aligned. |
hoverable | boolean | false | Open on hover instead of click. |
disabled | boolean | false | Disables the dropdown trigger. |
onActiveChange | (active: boolean) => void | — | Callback when dropdown active state changes. |
closeOnClick | boolean | true | Close dropdown when a menu item is clicked, or activated with Enter or Space. |
id | string | — | Root element ID (for aria-controls, etc). |
ref | React.Ref<HTMLDivElement> | — | Ref forwarded to the root dropdown element. |
... | All standard <div> attributes and Bulma helper props | — | See Helper Props |
Subcomponents:
Dropdown.Item: Bulma Dropdown item.Dropdown.Divider: Bulma Dropdown divider.
Dropdown.Item
| Prop | Type | Default | Description |
|---|---|---|---|
as | 'a' | 'div' | 'button' | 'a' | The element type to render. 'button' renders type="button" unless you pass type="submit" or type="reset", so an item inside a form does not submit it. |
active | boolean | false | Whether the item is active. |
className | string | — | Additional CSS classes. |
disabled | boolean | false | Marks the item as disabled; disabled items are skipped during keyboard navigation. Use with as="button" for a native disabled control, or pair with aria-disabled on a link. |
children | React.ReactNode | — | Item content. |
... | Remaining props of the element selected by as and Bulma helper props | — | See Helper Props |
Dropdown.Divider
No props. Renders as a menu divider (<hr>).
CSS & Sass Variables
Dropdown registers these variables on its own .dropdown element. Override them there (or via className) — a value set on an ancestor is only inherited, and loses to the component-level declaration. See Theme.
| CSS Variable | Sass Variable | Default |
|---|---|---|
--bulma-dropdown-menu-min-width | $dropdown-menu-min-width | 12rem |
--bulma-dropdown-content-background-color | $dropdown-content-background-color | var(--bulma-scheme-main) |
--bulma-dropdown-content-offset | $dropdown-content-offset | 0.25rem |
--bulma-dropdown-content-padding-bottom | $dropdown-content-padding-bottom | 0.5rem |
--bulma-dropdown-content-padding-top | $dropdown-content-padding-top | 0.5rem |
--bulma-dropdown-content-radius | $dropdown-content-radius | var(--bulma-radius) |
--bulma-dropdown-content-shadow | $dropdown-content-shadow | var(--bulma-shadow) |
--bulma-dropdown-content-z | $dropdown-content-z | 20 |
--bulma-dropdown-item-h | $dropdown-item-h | var(--bulma-scheme-h) |
--bulma-dropdown-item-s | $dropdown-item-s | var(--bulma-scheme-s) |
--bulma-dropdown-item-l | $dropdown-item-l | var(--bulma-scheme-main-l) |
--bulma-dropdown-item-background-l | $dropdown-item-background-l | var(--bulma-scheme-main-l) |
--bulma-dropdown-item-background-l-delta | $dropdown-item-background-l-delta | 0% |
--bulma-dropdown-item-hover-background-l-delta | $dropdown-item-hover-background-l-delta | var(--bulma-hover-background-l-delta) |
--bulma-dropdown-item-active-background-l-delta | $dropdown-item-active-background-l-delta | var(--bulma-active-background-l-delta) |
--bulma-dropdown-item-color-l | $dropdown-item-color-l | var(--bulma-text-strong-l) |
--bulma-dropdown-item-selected-h | $dropdown-item-selected-h | var(--bulma-link-h) |
--bulma-dropdown-item-selected-s | $dropdown-item-selected-s | var(--bulma-link-s) |
--bulma-dropdown-item-selected-l | $dropdown-item-selected-l | var(--bulma-link-l) |
--bulma-dropdown-item-selected-background-l | $dropdown-item-selected-background-l | var(--bulma-link-l) |
--bulma-dropdown-item-selected-color-l | $dropdown-item-selected-color-l | var(--bulma-link-invert-l) |
--bulma-dropdown-divider-background-color | $dropdown-divider-background-color | var(--bulma-border-weak) |