The BlockOptions component provides a dropdown menu for block-level actions like duplicating, deleting, or converting blocks. It’s typically triggered from FloatingBlockActions or custom UI elements.
Installation
Basic Usage
import { BlockOptions, useBlockActions } from '@yoopta/ui';
import { useState } from 'react';
function MyBlockOptions({ blockId, anchor }) {
const [open, setOpen] = useState(false);
const { duplicateBlock, deleteBlock } = useBlockActions();
return (
<BlockOptions open={open} onOpenChange={setOpen} anchor={anchor}>
<BlockOptions.Content>
<BlockOptions.Group>
<BlockOptions.Item onSelect={() => duplicateBlock(blockId)}>
Duplicate
</BlockOptions.Item>
<BlockOptions.Item
variant="destructive"
onSelect={() => deleteBlock(blockId)}
>
Delete
</BlockOptions.Item>
</BlockOptions.Group>
</BlockOptions.Content>
</BlockOptions>
);
}
Component API
BlockOptions (Root)
The root component that manages menu state.
Menu content (typically BlockOptions.Content)
Callback when open state changes
Default open state for uncontrolled usage
External anchor element for positioning (use when no Trigger is present)
BlockOptions.Trigger
Trigger button for opening the menu.
Merge props onto child element instead of wrapping
Additional CSS class name
BlockOptions.Content
The floating menu content.
side
'top' | 'right' | 'bottom' | 'left'
default:"'right'"
Placement relative to trigger
align
'start' | 'center' | 'end'
default:"'start'"
Alignment relative to trigger
Offset from trigger in pixels
Additional CSS class name
BlockOptions.Group
Groups related menu items.
Additional CSS class name
BlockOptions.Item
onSelect
(event: MouseEvent) => void
Called when item is selected
Whether the item is disabled
variant
'default' | 'destructive'
default:"'default'"
Visual variant
Keep menu open after selection
Additional CSS class name
BlockOptions.Separator
Visual separator between groups.
Additional CSS class name
BlockOptions.Label
Group label text.
Additional CSS class name
useBlockActions Hook
Helper hook providing common block actions:
const {
duplicateBlock, // (blockId: string) => void
copyBlockLink, // (blockId: string) => void
deleteBlock, // (blockId: string) => void
} = useBlockActions();
Examples
import { BlockOptions, useBlockActions } from '@yoopta/ui';
import { useState, useRef } from 'react';
import { Copy, Link, Trash2 } from 'lucide-react';
function CompleteBlockOptions({ blockId, anchor }) {
const [open, setOpen] = useState(false);
const { duplicateBlock, copyBlockLink, deleteBlock } = useBlockActions();
return (
<BlockOptions open={open} onOpenChange={setOpen} anchor={anchor}>
<BlockOptions.Content side="right" align="end">
<BlockOptions.Group>
<BlockOptions.Item
icon={<Copy size={16} />}
onSelect={() => duplicateBlock(blockId)}
>
Duplicate
</BlockOptions.Item>
<BlockOptions.Item
icon={<Link size={16} />}
onSelect={() => copyBlockLink(blockId)}
>
Copy link to block
</BlockOptions.Item>
</BlockOptions.Group>
<BlockOptions.Separator />
<BlockOptions.Group>
<BlockOptions.Item
icon={<Trash2 size={16} />}
variant="destructive"
onSelect={() => deleteBlock(blockId)}
>
Delete
</BlockOptions.Item>
</BlockOptions.Group>
</BlockOptions.Content>
</BlockOptions>
);
}
import { BlockOptions } from '@yoopta/ui';
import { ActionMenuList } from '@yoopta/ui';
import { useState, useRef } from 'react';
function OptionsWithTurnInto({ blockId, anchor }) {
const [optionsOpen, setOptionsOpen] = useState(false);
const [actionMenuOpen, setActionMenuOpen] = useState(false);
const turnIntoRef = useRef<HTMLButtonElement>(null);
const onTurnInto = () => {
setActionMenuOpen(true);
};
const onActionMenuClose = (open: boolean) => {
setActionMenuOpen(open);
if (!open) {
setOptionsOpen(false);
}
};
return (
<>
<BlockOptions open={optionsOpen} onOpenChange={setOptionsOpen} anchor={anchor}>
<BlockOptions.Content>
<BlockOptions.Group>
<BlockOptions.Item
ref={turnIntoRef}
onSelect={onTurnInto}
keepOpen
>
Turn into
</BlockOptions.Item>
</BlockOptions.Group>
<BlockOptions.Separator />
<BlockOptions.Group>
<BlockOptions.Item>Duplicate</BlockOptions.Item>
<BlockOptions.Item variant="destructive">Delete</BlockOptions.Item>
</BlockOptions.Group>
</BlockOptions.Content>
</BlockOptions>
<ActionMenuList
placement="right-start"
open={actionMenuOpen}
onOpenChange={onActionMenuClose}
anchor={turnIntoRef.current}
/>
</>
);
}
import { BlockOptions } from '@yoopta/ui';
import { MoreVertical } from 'lucide-react';
function OptionsWithTrigger() {
return (
<BlockOptions>
<BlockOptions.Trigger>
<button>
<MoreVertical size={16} />
</button>
</BlockOptions.Trigger>
<BlockOptions.Content>
<BlockOptions.Item>Duplicate</BlockOptions.Item>
<BlockOptions.Item>Delete</BlockOptions.Item>
</BlockOptions.Content>
</BlockOptions>
);
}
As Child Pattern
import { BlockOptions } from '@yoopta/ui';
import { Button } from './ui/button';
function OptionsWithAsChild() {
return (
<BlockOptions>
<BlockOptions.Trigger asChild>
<Button variant="ghost" size="sm">
Options
</Button>
</BlockOptions.Trigger>
<BlockOptions.Content>
{/* Items */}
</BlockOptions.Content>
</BlockOptions>
);
}
Custom Actions
import { BlockOptions } from '@yoopta/ui';
import { useYooptaEditor, Blocks } from '@yoopta/editor';
import { Eye, EyeOff, Lock } from 'lucide-react';
function CustomBlockOptions({ blockId, anchor }) {
const editor = useYooptaEditor();
const [open, setOpen] = useState(false);
const toggleVisibility = () => {
// Custom logic to toggle block visibility
const block = Blocks.getBlock(editor, { id: blockId });
if (!block) return;
Blocks.updateBlock(editor, {
id: blockId,
props: {
...block.props,
hidden: !block.props?.hidden,
},
});
};
return (
<BlockOptions open={open} onOpenChange={setOpen} anchor={anchor}>
<BlockOptions.Content>
<BlockOptions.Label>Visibility</BlockOptions.Label>
<BlockOptions.Group>
<BlockOptions.Item
icon={<Eye size={16} />}
onSelect={toggleVisibility}
>
Toggle visibility
</BlockOptions.Item>
<BlockOptions.Item icon={<Lock size={16} />}>
Lock block
</BlockOptions.Item>
</BlockOptions.Group>
</BlockOptions.Content>
</BlockOptions>
);
}
Conditional Items
import { BlockOptions } from '@yoopta/ui';
import { useYooptaEditor } from '@yoopta/editor';
function ConditionalOptions({ blockId, anchor }) {
const editor = useYooptaEditor();
const block = editor.children[blockId];
const canMoveUp = block?.meta.order > 0;
const canMoveDown = block?.meta.order < Object.keys(editor.children).length - 1;
return (
<BlockOptions anchor={anchor}>
<BlockOptions.Content>
<BlockOptions.Group>
{canMoveUp && (
<BlockOptions.Item>Move up</BlockOptions.Item>
)}
{canMoveDown && (
<BlockOptions.Item>Move down</BlockOptions.Item>
)}
</BlockOptions.Group>
</BlockOptions.Content>
</BlockOptions>
);
}
Behavior
The menu automatically closes when:
- An item is selected (unless
keepOpen={true})
- User clicks outside
- Escape key is pressed
- Anchor element is removed
Positioning
Uses Floating UI for smart positioning:
- Respects
side and align props
- Flips to opposite side if no space
- Shifts to stay within viewport
- Updates on scroll/resize
Focus Management
Provides keyboard navigation:
- Arrow keys to navigate items
- Enter to select
- Escape to close
- Tab to cycle through items
Styling
CSS Classes
.yoopta-ui-block-options {
/* Menu container */
}
.yoopta-ui-block-options-trigger {
/* Trigger button */
}
.yoopta-ui-block-options-group {
/* Item group */
}
.yoopta-ui-block-options-button {
/* Menu item */
}
.yoopta-ui-block-options-button-default {
/* Default item variant */
}
.yoopta-ui-block-options-button-destructive {
/* Destructive item variant */
}
.yoopta-ui-block-options-separator {
/* Separator line */
}
.yoopta-ui-block-options-label {
/* Group label */
}
Custom Styling
<BlockOptions>
<BlockOptions.Content className="my-menu">
<BlockOptions.Item className="my-item">
Custom styled item
</BlockOptions.Item>
</BlockOptions.Content>
</BlockOptions>
TypeScript
import type {
BlockOptionsRootProps,
BlockOptionsTriggerProps,
BlockOptionsContentProps,
BlockOptionsGroupProps,
BlockOptionsItemProps,
BlockOptionsSeparatorProps,
BlockOptionsLabelProps,
} from '@yoopta/ui';
See Also