Dropdown
Contextual menu triggered by a button for inline actions.
Overview
The Dropdown component is a core building block of the Byteion design system, providing a robust, accessibility-compliant interface for its specific UI capabilities.
Why this exists
Dropdowns organize actions and navigation into a compact, context-triggered menu. They prevent toolbar clutter by hiding secondary actions behind a single click.
Mental model
A dropdown is a contextual menu that appears near a trigger element. It contains links or actions. Clicking outside or pressing Escape dismisses it.
When to use
When not to use
Basic example
The following example demonstrates standard usage, fully compliant with Byteion's token-first approach.
import { Button, Dropdown, type DropdownItem } from '@byteion/react/client';
const items: DropdownItem[] = [
{ label: 'View profile', icon: <i className="ti ti-user" />, onSelect: () => {} },
{ label: 'Settings', icon: <i className="ti ti-settings" />, onSelect: () => {} },
{ type: 'separator' },
{ label: 'Sign out', icon: <i className="ti ti-logout" />, onSelect: () => {}, tone: 'danger' },
];
export default function DropdownDemo() {
return (
<div className="flex flex-wrap gap-3 p-6">
<Dropdown items={items} trigger={<Button variant="outline">Account ▾</Button>} />
<Dropdown items={items} placement="bottom-end"
trigger={<Button aria-label="More" iconOnly variant="ghost">
<i className="ti ti-dots-vertical" /></Button>} />
</div>
);
}
<script setup lang="ts">
import { Button, Dropdown, type DropdownItem } from '@byteion/vue';
const items: DropdownItem[] = [
{ label: 'View profile', icon: 'ti-user', onSelect: () => {} },
{ label: 'Settings', icon: 'ti-settings', onSelect: () => {} },
{ type: 'separator' },
{ label: 'Sign out', icon: 'ti-logout', onSelect: () => {}, tone: 'danger' },
];
</script>
<template>
<div class="flex flex-wrap gap-3 p-6">
<Dropdown :items="items">
<template #trigger>
<Button variant="outline">Account ▾</Button>
</template>
</Dropdown>
<Dropdown :items="items" placement="bottom-end">
<template #trigger>
<Button aria-label="More" icon-only variant="ghost">
<i class="ti ti-dots-vertical" />
</Button>
</template>
</Dropdown>
</div>
</template>
Variants
Each variant communicates a different meaning. Choose based on priority, not aesthetics.
Single default variant.
Sizes
Size should match the importance and context of the component.
- md — md size.
States
This component reacts seamlessly to the following interaction and data states:
- hidden — Not rendered in the DOM.
- visible — Rendered and interactive.
- default — Resting appearance before interaction.
- hover — Pointer is over the element — visual feedback via color or shadow.
- focus — Keyboard or tab focus — shows ring indicator for accessibility.
- active — Currently selected or in use.
- disabled — Non-interactive — grayed out, cursor not-allowed.
- open — Component is expanded or visible.
- closed — Component is collapsed or hidden.
Anatomy
Usage guidelines
- Close on click outside or Escape.
- Limit items to 7 ± 2.
- Group related items with headers or dividers.
Do and Don't
Do
- Close on outside click.
- Group related items.
- Limit to ~7 items.
Don't
- Do not nest dropdowns.
- Do not use for forms.
- Do not exceed ~7 items without grouping.
Accessibility
Trigger has aria-expanded and aria-controls. Menu has role="menu". Arrow keys navigate items. Escape closes.
Role:menu
ARIA Attributes
aria-haspopuparia-expanded
Keyboard Navigation
- ArrowDown: Moves focus to next item
- ArrowUp: Moves focus to previous item
- Enter: Activates or submits the element
- Escape: Closes the overlay or cancels the action
Customization
Panel follows popover tokens. Items use standard list styling.
Compose with React cva()
Use the exportedcva() +cn() utilities to compose your own variant on top of the Byteion tokens.
import { cva, cn, type VariantProps } from '@byteion/react';
const myDropdown = cva('base classes…', {
variants: {
accent: { soft:'border-primary-200', loud:'border-primary-500' },
},
defaultVariants: { accent:'soft' },
});
type Props = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof myDropdown>;
Compose with Vue cva()
Use the exportedcva() +cn() utilities inside standard Vue setup blocks.
<script setup lang="ts">
import { computed, useAttrs } from 'vue';
import { cva, type VariantProps } from '@byteion/vue';
interface Props { accent?:'soft' |'loud' }
const props = withDefaults(defineProps<Props>(), { accent:'soft' });
const attrs = useAttrs();
const myDropdown = cva('base classes…', {
variants: { accent: { soft:'border-primary-200', loud:'border-primary-500' } },
});
const cls = computed(() => myDropdown({ accent: props.accent, class: attrs.class as string | undefined }));
</script>
Runtime events
Byteion keeps framework APIs idiomatic: HTML uses DOM custom events, React uses callback props, and Vue uses emitted events / v-model updates.
HTML / JS events
bi:dropdown:hidebi:dropdown:show
React callbacks
onOpenChange(open: boolean) => void
Vue emits
update:open[v: boolean]
Remote / AJAX
This component does not implement Byteion JS remote loading. Use normal HTML form submission, framework state, or your application data layer around it.
API Reference
Requires data-bi-component="dropdown". Toggle with data-bi-dropdown-toggle.
HTML Data Attributes
data-bi-dropdowndata-bi-dropdown-toggle
React Props · DropdownProps
Extracted directly fromreact/src/components/Dropdown.tsx.
export interface DropdownProps {
/** The trigger button (must accept onClick + aria-* via cloneElement). */
trigger: ReactElement<any>;
items: DropdownItem[];
/** Menu alignment relative to the trigger. */
align?: 'start' | 'end';
/** Menu side. */
side?: 'top' | 'bottom';
/** Controlled open state. */
open?: boolean;
onOpenChange?: (open: boolean) => void;
className?: string;
}
Vue Props · DropdownProps
Extracted directly fromvue/src/components/Dropdown.vue.
export interface DropdownProps {
items: DropdownItem[];
/** Controlled open. Use v-model:open. */
open?: boolean;
defaultOpen?: boolean;
align?: 'start' | 'end';
side?: 'top' | 'bottom';
}
Developer tips
Common mistakes
Production usage tips
1. Static HTML dropdowns require @byteion/js for data-bi-dropdown behavior. React and Vue manage behavior internally.
2. Test inside tables, cards, navbars, modals, and scroll containers. The overflow: hidden on parent containers is the most common cause of clipped menus.
3. Test near viewport edges — the menu should not overflow the visible area. Use side="top" or align="end" to adjust positioning.
4. Test keyboard navigation: ArrowDown, ArrowUp, Home, End (React only), Escape, Enter.
5. Test dark mode and brand themes — the dropdown uses bg-popover/text-popover-foreground tokens that respond to theme changes.
6. For destructive actions that need confirmation, pair the dropdown item with a Modal. Do not execute irreversible actions from a dropdown without confirmation.
7. Keep React/Vue item shapes aligned with core schema. React uses separatorBefore for dividers; Vue currently does not support item-level separators.
Premium examples
Advanced patterns available in Byteion Pro:
Related components
Unlock Premium Patterns
Get access to copy-paste ready Pro blocks, SaaS templates, and advanced data-tables built with this component.
Explore Pro Components