Heading 1
This is a top-level heading, typically used for main titles and major section headers.
Heading 2
Secondary headings help organize content into clear sections and subsections.
Heading 3
Third-level headings provide further content structure and hierarchy.
"Blockquotes are perfect for highlighting important information, quotes from external sources, or emphasizing key points in your content."
Use headings to create a clear document structure that helps readers navigate your content effectively. Combine them with blockquotes to emphasize important information.
Horizontal rules help visually separate different sections of your content, creating clear breaks between topics or ideas.
Text Formatting
Add style and emphasis to your text using various formatting options.
Make text bold, italic, underlined, or apply a combination of these styles for emphasis.
Add strikethrough to indicate deleted content, use
inline code for technical terms, or highlight important information.Format mathematical expressions with subscript and superscript text.
Show keyboard shortcuts like ⌘ + B for bold or ⌘ + I for italic formatting.
1
100%
Files
'use client';
import * as React from 'react';
import { Plate, usePlateEditor } from 'platejs/react';
import { EditorKit } from '@/components/editor/editor-kit';
import { Editor, EditorContainer } from '@/components/ui/editor';
import { DEMO_VALUES } from './values/demo-values';
export default function Demo({ id }: { id: string }) {
const editor = usePlateEditor({
plugins: EditorKit,
value: DEMO_VALUES[id],
});
return (
<Plate editor={editor}>
<EditorContainer variant="demo">
<Editor />
</EditorContainer>
</Plate>
);
}
Basic block elements and text marks.
basic-nodes-demo


功能特性
- 固定工具栏: 持久固定在编辑器顶部的工具栏
- 浮动工具栏: 文本选中时出现的上下文工具栏
- 可定制按钮: 轻松添加、删除和重新排序工具栏按钮
- 响应式设计: 适配不同屏幕尺寸和内容
- 插件集成: 与Plate插件和UI组件无缝集成
套件使用
安装
最快捷的方式是使用FixedToolbarKit和FloatingToolbarKit,它们包含预配置的工具栏插件及其Plate UI组件。
'use client';
import { createPlatePlugin } from 'platejs/react';
import { FixedToolbar } from '@/components/ui/fixed-toolbar';
import { FixedToolbarButtons } from '@/components/ui/fixed-toolbar-buttons';
export const FixedToolbarKit = [
createPlatePlugin({
key: 'fixed-toolbar',
render: {
beforeEditable: () => (
<FixedToolbar>
<FixedToolbarButtons />
</FixedToolbar>
),
},
}),
];
'use client';
import { createPlatePlugin } from 'platejs/react';
import { FloatingToolbar } from '@/components/ui/floating-toolbar';
import { FloatingToolbarButtons } from '@/components/ui/floating-toolbar-buttons';
export const FloatingToolbarKit = [
createPlatePlugin({
key: 'floating-toolbar',
render: {
afterEditable: () => (
<FloatingToolbar>
<FloatingToolbarButtons />
</FloatingToolbar>
),
},
}),
];
FixedToolbar: 在编辑器上方渲染固定工具栏FixedToolbarButtons: 固定工具栏的预配置按钮集FloatingToolbar: 文本选中时渲染浮动工具栏FloatingToolbarButtons: 浮动工具栏的预配置按钮集
添加套件
import { createPlateEditor } from 'platejs/react';
import { FixedToolbarKit } from '@/components/editor/plugins/fixed-toolbar-kit';
import { FloatingToolbarKit } from '@/components/editor/plugins/floating-toolbar-kit';
const editor = createPlateEditor({
plugins: [
// ...其他插件
...FixedToolbarKit,
...FloatingToolbarKit,
],
});手动配置
创建插件
import { createPlatePlugin } from 'platejs/react';
import { FixedToolbar } from '@/components/ui/fixed-toolbar';
import { FixedToolbarButtons } from '@/components/ui/fixed-toolbar-buttons';
import { FloatingToolbar } from '@/components/ui/floating-toolbar';
import { FloatingToolbarButtons } from '@/components/ui/floating-toolbar-buttons';
const fixedToolbarPlugin = createPlatePlugin({
key: 'fixed-toolbar',
render: {
beforeEditable: () => (
<FixedToolbar>
<FixedToolbarButtons />
</FixedToolbar>
),
},
});
const floatingToolbarPlugin = createPlatePlugin({
key: 'floating-toolbar',
render: {
afterEditable: () => (
<FloatingToolbar>
<FloatingToolbarButtons />
</FloatingToolbar>
),
},
});
const editor = createPlateEditor({
plugins: [
// ...其他插件
fixedToolbarPlugin,
floatingToolbarPlugin,
],
});render.beforeEditable: 在编辑器内容上方渲染FixedToolbarrender.afterEditable: 在编辑器后渲染FloatingToolbar作为覆盖层
自定义固定工具栏按钮
FixedToolbarButtons组件包含固定工具栏的默认按钮集。
'use client';
import * as React from 'react';
import {
ArrowUpToLineIcon,
BaselineIcon,
BoldIcon,
Code2Icon,
HighlighterIcon,
ItalicIcon,
PaintBucketIcon,
StrikethroughIcon,
UnderlineIcon,
WandSparklesIcon,
} from 'lucide-react';
import { KEYS } from 'platejs';
import { useEditorReadOnly } from 'platejs/react';
import { AIToolbarButton } from './ai-toolbar-button';
import { AlignToolbarButton } from './align-toolbar-button';
import { CommentToolbarButton } from './comment-toolbar-button';
import { EmojiToolbarButton } from './emoji-toolbar-button';
import { ExportToolbarButton } from './export-toolbar-button';
import { FontColorToolbarButton } from './font-color-toolbar-button';
import { FontSizeToolbarButton } from './font-size-toolbar-button';
import { RedoToolbarButton, UndoToolbarButton } from './history-toolbar-button';
import { ImportToolbarButton } from './import-toolbar-button';
import {
IndentToolbarButton,
OutdentToolbarButton,
} from './indent-toolbar-button';
import { InsertToolbarButton } from './insert-toolbar-button';
import { LineHeightToolbarButton } from './line-height-toolbar-button';
import { LinkToolbarButton } from './link-toolbar-button';
import {
BulletedListToolbarButton,
NumberedListToolbarButton,
TodoListToolbarButton,
} from './list-toolbar-button';
import { MarkToolbarButton } from './mark-toolbar-button';
import { MediaToolbarButton } from './media-toolbar-button';
import { ModeToolbarButton } from './mode-toolbar-button';
import { MoreToolbarButton } from './more-toolbar-button';
import { TableToolbarButton } from './table-toolbar-button';
import { ToggleToolbarButton } from './toggle-toolbar-button';
import { ToolbarGroup } from './toolbar';
import { TurnIntoToolbarButton } from './turn-into-toolbar-button';
export function FixedToolbarButtons() {
const readOnly = useEditorReadOnly();
return (
<div className="flex w-full">
{!readOnly && (
<>
<ToolbarGroup>
<UndoToolbarButton />
<RedoToolbarButton />
</ToolbarGroup>
<ToolbarGroup>
<AIToolbarButton tooltip="AI commands">
<WandSparklesIcon />
</AIToolbarButton>
</ToolbarGroup>
<ToolbarGroup>
<ExportToolbarButton>
<ArrowUpToLineIcon />
</ExportToolbarButton>
<ImportToolbarButton />
</ToolbarGroup>
<ToolbarGroup>
<InsertToolbarButton />
<TurnIntoToolbarButton />
<FontSizeToolbarButton />
</ToolbarGroup>
<ToolbarGroup>
<MarkToolbarButton nodeType={KEYS.bold} tooltip="Bold (⌘+B)">
<BoldIcon />
</MarkToolbarButton>
<MarkToolbarButton nodeType={KEYS.italic} tooltip="Italic (⌘+I)">
<ItalicIcon />
</MarkToolbarButton>
<MarkToolbarButton
nodeType={KEYS.underline}
tooltip="Underline (⌘+U)"
>
<UnderlineIcon />
</MarkToolbarButton>
<MarkToolbarButton
nodeType={KEYS.strikethrough}
tooltip="Strikethrough (⌘+⇧+M)"
>
<StrikethroughIcon />
</MarkToolbarButton>
<MarkToolbarButton nodeType={KEYS.code} tooltip="Code (⌘+E)">
<Code2Icon />
</MarkToolbarButton>
<FontColorToolbarButton nodeType={KEYS.color} tooltip="Text color">
<BaselineIcon />
</FontColorToolbarButton>
<FontColorToolbarButton
nodeType={KEYS.backgroundColor}
tooltip="Background color"
>
<PaintBucketIcon />
</FontColorToolbarButton>
</ToolbarGroup>
<ToolbarGroup>
<AlignToolbarButton />
<NumberedListToolbarButton />
<BulletedListToolbarButton />
<TodoListToolbarButton />
<ToggleToolbarButton />
</ToolbarGroup>
<ToolbarGroup>
<LinkToolbarButton />
<TableToolbarButton />
<EmojiToolbarButton />
</ToolbarGroup>
<ToolbarGroup>
<MediaToolbarButton nodeType={KEYS.img} />
<MediaToolbarButton nodeType={KEYS.video} />
<MediaToolbarButton nodeType={KEYS.audio} />
<MediaToolbarButton nodeType={KEYS.file} />
</ToolbarGroup>
<ToolbarGroup>
<LineHeightToolbarButton />
<OutdentToolbarButton />
<IndentToolbarButton />
</ToolbarGroup>
<ToolbarGroup>
<MoreToolbarButton />
</ToolbarGroup>
</>
)}
<div className="grow" />
<ToolbarGroup>
<MarkToolbarButton nodeType={KEYS.highlight} tooltip="Highlight">
<HighlighterIcon />
</MarkToolbarButton>
<CommentToolbarButton />
</ToolbarGroup>
<ToolbarGroup>
<ModeToolbarButton />
</ToolbarGroup>
</div>
);
}
可以通过编辑components/ui/fixed-toolbar-buttons.tsx来自定义。
自定义浮动工具栏按钮
同样地,可以通过编辑components/ui/floating-toolbar-buttons.tsx来自定义浮动工具栏。
'use client';
import * as React from 'react';
import {
BoldIcon,
Code2Icon,
ItalicIcon,
StrikethroughIcon,
UnderlineIcon,
WandSparklesIcon,
} from 'lucide-react';
import { KEYS } from 'platejs';
import { useEditorReadOnly } from 'platejs/react';
import { AIToolbarButton } from './ai-toolbar-button';
import { CommentToolbarButton } from './comment-toolbar-button';
import { InlineEquationToolbarButton } from './equation-toolbar-button';
import { LinkToolbarButton } from './link-toolbar-button';
import { MarkToolbarButton } from './mark-toolbar-button';
import { MoreToolbarButton } from './more-toolbar-button';
import { SuggestionToolbarButton } from './suggestion-toolbar-button';
import { ToolbarGroup } from './toolbar';
import { TurnIntoToolbarButton } from './turn-into-toolbar-button';
export function FloatingToolbarButtons() {
const readOnly = useEditorReadOnly();
return (
<>
{!readOnly && (
<>
<ToolbarGroup>
<AIToolbarButton tooltip="AI commands">
<WandSparklesIcon />
Ask AI
</AIToolbarButton>
</ToolbarGroup>
<ToolbarGroup>
<TurnIntoToolbarButton />
<MarkToolbarButton nodeType={KEYS.bold} tooltip="Bold (⌘+B)">
<BoldIcon />
</MarkToolbarButton>
<MarkToolbarButton nodeType={KEYS.italic} tooltip="Italic (⌘+I)">
<ItalicIcon />
</MarkToolbarButton>
<MarkToolbarButton
nodeType={KEYS.underline}
tooltip="Underline (⌘+U)"
>
<UnderlineIcon />
</MarkToolbarButton>
<MarkToolbarButton
nodeType={KEYS.strikethrough}
tooltip="Strikethrough (⌘+⇧+M)"
>
<StrikethroughIcon />
</MarkToolbarButton>
<MarkToolbarButton nodeType={KEYS.code} tooltip="Code (⌘+E)">
<Code2Icon />
</MarkToolbarButton>
<InlineEquationToolbarButton />
<LinkToolbarButton />
</ToolbarGroup>
</>
)}
<ToolbarGroup>
<CommentToolbarButton />
<SuggestionToolbarButton />
{!readOnly && <MoreToolbarButton />}
</ToolbarGroup>
</>
);
}
创建自定义按钮
这个示例展示了一个向编辑器插入自定义文本的按钮。
import { useEditorRef } from 'platejs/react';
import { CustomIcon } from 'lucide-react';
import { ToolbarButton } from '@/components/ui/toolbar';
export function CustomToolbarButton() {
const editor = useEditorRef();
return (
<ToolbarButton
onClick={() => {
// 自定义操作
editor.tf.insertText('自定义文本');
}}
tooltip="自定义操作"
>
<CustomIcon />
</ToolbarButton>
);
}创建标记按钮
对于切换粗体或斜体等标记,可以使用MarkToolbarButton组件。它会自动处理切换状态和操作。
这个示例创建了一个"粗体"按钮。
import { BoldIcon } from 'lucide-react';
import { MarkToolbarButton } from '@/components/ui/mark-toolbar-button';
export function BoldToolbarButton() {
return (
<MarkToolbarButton nodeType="bold" tooltip="粗体 (⌘+B)">
<BoldIcon />
</MarkToolbarButton>
);
}nodeType: 指定要切换的标记类型(如bold、italic)tooltip: 为按钮提供提示信息MarkToolbarButton使用useMarkToolbarButtonState获取切换状态,使用useMarkToolbarButton获取onClick处理器和其他属性
转换工具栏按钮
TurnIntoToolbarButton提供下拉菜单将当前块转换为不同类型(标题、列表、引用等)。
'use client';
import * as React from 'react';
import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';
import type { TElement } from 'platejs';
import { DropdownMenuItemIndicator } from '@radix-ui/react-dropdown-menu';
import {
CheckIcon,
ChevronRightIcon,
Columns3Icon,
FileCodeIcon,
Heading1Icon,
Heading2Icon,
Heading3Icon,
ListIcon,
ListOrderedIcon,
PilcrowIcon,
QuoteIcon,
SquareIcon,
} from 'lucide-react';
import { KEYS } from 'platejs';
import { useEditorRef, useSelectionFragmentProp } from 'platejs/react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
getBlockType,
setBlockType,
} from '@/components/editor/transforms';
import { ToolbarButton, ToolbarMenuGroup } from './toolbar';
export const turnIntoItems = [
{
icon: <PilcrowIcon />,
keywords: ['paragraph'],
label: 'Text',
value: KEYS.p,
},
{
icon: <Heading1Icon />,
keywords: ['title', 'h1'],
label: 'Heading 1',
value: 'h1',
},
{
icon: <Heading2Icon />,
keywords: ['subtitle', 'h2'],
label: 'Heading 2',
value: 'h2',
},
{
icon: <Heading3Icon />,
keywords: ['subtitle', 'h3'],
label: 'Heading 3',
value: 'h3',
},
{
icon: <ListIcon />,
keywords: ['unordered', 'ul', '-'],
label: 'Bulleted list',
value: KEYS.ul,
},
{
icon: <ListOrderedIcon />,
keywords: ['ordered', 'ol', '1'],
label: 'Numbered list',
value: KEYS.ol,
},
{
icon: <SquareIcon />,
keywords: ['checklist', 'task', 'checkbox', '[]'],
label: 'To-do list',
value: KEYS.listTodo,
},
{
icon: <ChevronRightIcon />,
keywords: ['collapsible', 'expandable'],
label: 'Toggle list',
value: KEYS.toggle,
},
{
icon: <FileCodeIcon />,
keywords: ['```'],
label: 'Code',
value: KEYS.codeBlock,
},
{
icon: <QuoteIcon />,
keywords: ['citation', 'blockquote', '>'],
label: 'Quote',
value: KEYS.blockquote,
},
{
icon: <Columns3Icon />,
label: '3 columns',
value: 'action_three_columns',
},
];
export function TurnIntoToolbarButton(props: DropdownMenuProps) {
const editor = useEditorRef();
const [open, setOpen] = React.useState(false);
const value = useSelectionFragmentProp({
defaultValue: KEYS.p,
getProp: (node) => getBlockType(node as TElement),
});
const selectedItem = React.useMemo(
() =>
turnIntoItems.find((item) => item.value === (value ?? KEYS.p)) ??
turnIntoItems[0],
[value]
);
return (
<DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>
<DropdownMenuTrigger asChild>
<ToolbarButton
className="min-w-[125px]"
pressed={open}
tooltip="Turn into"
isDropdown
>
{selectedItem.label}
</ToolbarButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="ignore-click-outside/toolbar min-w-0"
onCloseAutoFocus={(e) => {
e.preventDefault();
editor.tf.focus();
}}
align="start"
>
<ToolbarMenuGroup
value={value}
onValueChange={(type) => {
setBlockType(editor, type);
}}
label="Turn into"
>
{turnIntoItems.map(({ icon, label, value: itemValue }) => (
<DropdownMenuRadioItem
key={itemValue}
className="min-w-[180px] pl-2 *:first:[span]:hidden"
value={itemValue}
>
<span className="pointer-events-none absolute right-2 flex size-3.5 items-center justify-center">
<DropdownMenuItemIndicator>
<CheckIcon />
</DropdownMenuItemIndicator>
</span>
{icon}
{label}
</DropdownMenuRadioItem>
))}
</ToolbarMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}
要添加新的块类型选项,编辑turnIntoItems数组:
const turnIntoItems = [
// ... 现有项
{
icon: <CustomIcon />,
keywords: ['custom', 'special'],
label: '自定义块',
value: 'custom-block',
},
];插入工具栏按钮
InsertToolbarButton提供下拉菜单插入各种元素(块、列表、媒体、内联元素)。
'use client';
import * as React from 'react';
import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';
import {
CalendarIcon,
ChevronRightIcon,
Columns3Icon,
FileCodeIcon,
FilmIcon,
Heading1Icon,
Heading2Icon,
Heading3Icon,
ImageIcon,
Link2Icon,
ListIcon,
ListOrderedIcon,
MinusIcon,
PilcrowIcon,
PlusIcon,
QuoteIcon,
RadicalIcon,
SquareIcon,
TableIcon,
TableOfContentsIcon,
} from 'lucide-react';
import { KEYS } from 'platejs';
import { type PlateEditor, useEditorRef } from 'platejs/react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
insertBlock,
insertInlineElement,
} from '@/components/editor/transforms';
import { ToolbarButton, ToolbarMenuGroup } from './toolbar';
type Group = {
group: string;
items: Item[];
};
interface Item {
icon: React.ReactNode;
value: string;
onSelect: (editor: PlateEditor, value: string) => void;
focusEditor?: boolean;
label?: string;
}
const groups: Group[] = [
{
group: 'Basic blocks',
items: [
{
icon: <PilcrowIcon />,
label: 'Paragraph',
value: KEYS.p,
},
{
icon: <Heading1Icon />,
label: 'Heading 1',
value: 'h1',
},
{
icon: <Heading2Icon />,
label: 'Heading 2',
value: 'h2',
},
{
icon: <Heading3Icon />,
label: 'Heading 3',
value: 'h3',
},
{
icon: <TableIcon />,
label: 'Table',
value: KEYS.table,
},
{
icon: <FileCodeIcon />,
label: 'Code',
value: KEYS.codeBlock,
},
{
icon: <QuoteIcon />,
label: 'Quote',
value: KEYS.blockquote,
},
{
icon: <MinusIcon />,
label: 'Divider',
value: KEYS.hr,
},
].map((item) => ({
...item,
onSelect: (editor, value) => {
insertBlock(editor, value);
},
})),
},
{
group: 'Lists',
items: [
{
icon: <ListIcon />,
label: 'Bulleted list',
value: KEYS.ul,
},
{
icon: <ListOrderedIcon />,
label: 'Numbered list',
value: KEYS.ol,
},
{
icon: <SquareIcon />,
label: 'To-do list',
value: KEYS.listTodo,
},
{
icon: <ChevronRightIcon />,
label: 'Toggle list',
value: KEYS.toggle,
},
].map((item) => ({
...item,
onSelect: (editor, value) => {
insertBlock(editor, value);
},
})),
},
{
group: 'Media',
items: [
{
icon: <ImageIcon />,
label: 'Image',
value: KEYS.img,
},
{
icon: <FilmIcon />,
label: 'Embed',
value: KEYS.mediaEmbed,
},
].map((item) => ({
...item,
onSelect: (editor, value) => {
insertBlock(editor, value);
},
})),
},
{
group: 'Advanced blocks',
items: [
{
icon: <TableOfContentsIcon />,
label: 'Table of contents',
value: KEYS.toc,
},
{
icon: <Columns3Icon />,
label: '3 columns',
value: 'action_three_columns',
},
{
focusEditor: false,
icon: <RadicalIcon />,
label: 'Equation',
value: KEYS.equation,
},
].map((item) => ({
...item,
onSelect: (editor, value) => {
insertBlock(editor, value);
},
})),
},
{
group: 'Inline',
items: [
{
icon: <Link2Icon />,
label: 'Link',
value: KEYS.link,
},
{
focusEditor: true,
icon: <CalendarIcon />,
label: 'Date',
value: KEYS.date,
},
{
focusEditor: false,
icon: <RadicalIcon />,
label: 'Inline Equation',
value: KEYS.inlineEquation,
},
].map((item) => ({
...item,
onSelect: (editor, value) => {
insertInlineElement(editor, value);
},
})),
},
];
export function InsertToolbarButton(props: DropdownMenuProps) {
const editor = useEditorRef();
const [open, setOpen] = React.useState(false);
return (
<DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>
<DropdownMenuTrigger asChild>
<ToolbarButton pressed={open} tooltip="Insert" isDropdown>
<PlusIcon />
</ToolbarButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="flex max-h-[500px] min-w-0 flex-col overflow-y-auto"
align="start"
>
{groups.map(({ group, items: nestedItems }) => (
<ToolbarMenuGroup key={group} label={group}>
{nestedItems.map(({ icon, label, value, onSelect }) => (
<DropdownMenuItem
key={value}
className="min-w-[180px]"
onSelect={() => {
onSelect(editor, value);
editor.tf.focus();
}}
>
{icon}
{label}
</DropdownMenuItem>
))}
</ToolbarMenuGroup>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
要添加新的可插入项,将其添加到groups数组的相应分组中:
{
group: '基础块',
items: [
// ... 现有项
{
icon: <CustomIcon />,
label: '自定义块',
value: 'custom-block',
},
].map((item) => ({
...item,
onSelect: (editor, value) => {
insertBlock(editor, value);
},
})),
}插件
FixedToolbarKit
在编辑器内容上方渲染固定工具栏的插件。
FloatingToolbarKit
在文本选中时渲染浮动工具栏的插件。