From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Stream
    • Copilot
  • Discussion
    • Comments
    • Suggestion
    • Basic Blocks
      • Blockquote
      • Heading
      • Horizontal Rule
    • Callout
    • Code Block
    • Column
    • Date
    • Equation
    • Link
    • List Classic
    • Media
    • MentionElement
    • Table
    • Table of Contents
    • Footnote
    • Toggle
  • Marks
    • Bold
    • Italic
    • Underline
    • Code
    • Highlight
    • Keyboard Input
    • Strikethrough
    • Subscript
    • Superscript
      • Font
      • Line Height
      • Text Align
    • Indent
    • List
      • Exit Break
      • Single Block
      • Trailing Block
    • Autoformat
    • Block Menu
    • Block Placeholder
    • Combobox
      • Emoji
      • MentionElement
      • Slash Command
    • Cursor Overlay
    • Drag & Drop
    • Navigation Feedback
    • Tabbable
    • Toolbar
    • Yjs
    • Multi SelectEditor
    • CSV
    • DOCX
    • HTML
    • Markdown

Code Block

PreviousNext

Display code with syntax highlighting.

PlusCode Block Element
Loading…
CalloutColumn

On This Page

FeaturesKit UsageInstallationAdd KitManual UsageInstallationAdd PluginsConfigure PluginsTurn Into Toolbar ButtonInsert Toolbar ButtonPluginsCodeBlockPluginCodeHighlightPluginCodeLinePluginCommandsQueriesisCodeBlockEmptyisSelectionAtCodeBlockStart
Build your editor
Production-ready AI template and reusable components.
Get all-access

Features

  • Syntax highlighting for code blocks
  • Support for multiple programming languages
  • Customizable language selection
  • Proper indentation handling
  • Markdown code fence shortcuts through CodeBlockRules.markdown({ on })
Report an issue

Kit Usage

Installation

The fastest way to add code blocks is with CodeBlockKit. It composes CodeBlockPlugin, its required CodeLinePlugin, and the removable CodeHighlightPlugin with the shipped triple-backtick input rule and Plate UI components.

'use client';
 
import { BaseCodeBlockPlugin, CodeBlockRules } from '@platejs/code-block';
import {
  CodeBlockPlugin,
  CodeHighlightPlugin,
  CodeLinePlugin,
} from '@platejs/code-block/react';
import { all, createLowlight } from 'lowlight';
import { BracesIcon, Check, CheckIcon, CopyIcon } from 'lucide-react';
import { NodeApi } from 'platejs';
import {
  PlateElement,
  PlateLeaf,
  type PlateElementProps,
  type PlateLeafProps,
  useEditor,
  useEditorReadOnly,
  useElement,
} from 'platejs/react';
import * as React from 'react';
 
import { Button } from '@/components/ui/button';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/components/ui/command';
import { cn } from '@/lib/utils';
import {
  FloatingPopover as Popover,
  FloatingPopoverContent as PopoverContent,
  FloatingPopoverTrigger as PopoverTrigger,
} from '@/components/editor/floating-popover';
 
type CodeBlockElementProps = PlateElementProps<typeof CodeBlockPlugin> & {
  showLanguageLabel?: boolean;
};
 
const codeBlockLanguages: Array<{ label: string; value: string }> = [
  { label: 'Auto', value: 'auto' },
  { label: 'Plain Text', value: 'plaintext' },
  { label: 'ABAP', value: 'abap' },
  { label: 'Agda', value: 'agda' },
  { label: 'Arduino', value: 'arduino' },
  { label: 'ASCII Art', value: 'ascii' },
  { label: 'Assembly', value: 'x86asm' },
  { label: 'Bash', value: 'bash' },
  { label: 'BASIC', value: 'basic' },
  { label: 'BNF', value: 'bnf' },
  { label: 'C', value: 'c' },
  { label: 'C#', value: 'csharp' },
  { label: 'C++', value: 'cpp' },
  { label: 'Clojure', value: 'clojure' },
  { label: 'CoffeeScript', value: 'coffeescript' },
  { label: 'Coq', value: 'coq' },
  { label: 'CSS', value: 'css' },
  { label: 'Dart', value: 'dart' },
  { label: 'Dhall', value: 'dhall' },
  { label: 'Diff', value: 'diff' },
  { label: 'Docker', value: 'dockerfile' },
  { label: 'EBNF', value: 'ebnf' },
  { label: 'Elixir', value: 'elixir' },
  { label: 'Elm', value: 'elm' },
  { label: 'Erlang', value: 'erlang' },
  { label: 'F#', value: 'fsharp' },
  { label: 'Flow', value: 'flow' },
  { label: 'Fortran', value: 'fortran' },
  { label: 'Gherkin', value: 'gherkin' },
  { label: 'GLSL', value: 'glsl' },
  { label: 'Go', value: 'go' },
  { label: 'GraphQL', value: 'graphql' },
  { label: 'Groovy', value: 'groovy' },
  { label: 'Haskell', value: 'haskell' },
  { label: 'HCL', value: 'hcl' },
  { label: 'HTML', value: 'html' },
  { label: 'Idris', value: 'idris' },
  { label: 'Java', value: 'java' },
  { label: 'JavaScript', value: 'javascript' },
  { label: 'JSON', value: 'json' },
  { label: 'Julia', value: 'julia' },
  { label: 'Kotlin', value: 'kotlin' },
  { label: 'LaTeX', value: 'latex' },
  { label: 'Less', value: 'less' },
  { label: 'Lisp', value: 'lisp' },
  { label: 'LiveScript', value: 'livescript' },
  { label: 'LLVM IR', value: 'llvm' },
  { label: 'Lua', value: 'lua' },
  { label: 'Makefile', value: 'makefile' },
  { label: 'Markdown', value: 'markdown' },
  { label: 'Markup', value: 'markup' },
  { label: 'MATLAB', value: 'matlab' },
  { label: 'Mathematica', value: 'mathematica' },
  { label: 'Mermaid', value: 'mermaid' },
  { label: 'Nix', value: 'nix' },
  { label: 'Notion Formula', value: 'notion' },
  { label: 'Objective-C', value: 'objectivec' },
  { label: 'OCaml', value: 'ocaml' },
  { label: 'Pascal', value: 'pascal' },
  { label: 'Perl', value: 'perl' },
  { label: 'PHP', value: 'php' },
  { label: 'PowerShell', value: 'powershell' },
  { label: 'Prolog', value: 'prolog' },
  { label: 'Protocol Buffers', value: 'protobuf' },
  { label: 'PureScript', value: 'purescript' },
  { label: 'Python', value: 'python' },
  { label: 'R', value: 'r' },
  { label: 'Racket', value: 'racket' },
  { label: 'Reason', value: 'reasonml' },
  { label: 'Ruby', value: 'ruby' },
  { label: 'Rust', value: 'rust' },
  { label: 'Sass', value: 'scss' },
  { label: 'Scala', value: 'scala' },
  { label: 'Scheme', value: 'scheme' },
  { label: 'SCSS', value: 'scss' },
  { label: 'Shell', value: 'shell' },
  { label: 'Smalltalk', value: 'smalltalk' },
  { label: 'Solidity', value: 'solidity' },
  { label: 'SQL', value: 'sql' },
  { label: 'Swift', value: 'swift' },
  { label: 'TOML', value: 'toml' },
  { label: 'TypeScript', value: 'typescript' },
  { label: 'VB.Net', value: 'vbnet' },
  { label: 'Verilog', value: 'verilog' },
  { label: 'VHDL', value: 'vhdl' },
  { label: 'Visual Basic', value: 'vbnet' },
  { label: 'WebAssembly', value: 'wasm' },
  { label: 'XML', value: 'xml' },
  { label: 'YAML', value: 'yaml' },
];
 
function getCodeBlockLanguageLabel(lang?: string | null) {
  const value = lang?.trim();
 
  if (!value) return null;
 
  return (
    codeBlockLanguages.find((language) => language.value === value)?.label ??
    value
  );
}
 
export function CodeBlockElement({
  showLanguageLabel = true,
  ...props
}: CodeBlockElementProps) {
  const { editor, element } = props;
 
  return (
    <PlateElement
      className="py-1 **:[.hljs-addition]:bg-[#f0fff4] **:[.hljs-addition]:text-[#22863a] dark:**:[.hljs-addition]:bg-[#3c5743] dark:**:[.hljs-addition]:text-[#ceead5] **:[.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable]:text-[#005cc5] dark:**:[.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable]:text-[#6596cf] **:[.hljs-built\\\\_in,.hljs-symbol]:text-[#e36209] dark:**:[.hljs-built\\\\_in,.hljs-symbol]:text-[#c3854e] **:[.hljs-bullet]:text-[#735c0f] **:[.hljs-comment,.hljs-code,.hljs-formula]:text-[#6a737d] dark:**:[.hljs-comment,.hljs-code,.hljs-formula]:text-[#6a737d] **:[.hljs-deletion]:bg-[#ffeef0] **:[.hljs-deletion]:text-[#b31d28] dark:**:[.hljs-deletion]:bg-[#473235] dark:**:[.hljs-deletion]:text-[#e7c7cb] **:[.hljs-emphasis]:italic **:[.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\_]:text-[#d73a49] dark:**:[.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\_]:text-[#ee6960] **:[.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo]:text-[#22863a] dark:**:[.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo]:text-[#36a84f] **:[.hljs-regexp,.hljs-string,.hljs-meta_.hljs-string]:text-[#032f62] dark:**:[.hljs-regexp,.hljs-string,.hljs-meta_.hljs-string]:text-[#3593ff] **:[.hljs-section]:font-bold **:[.hljs-section]:text-[#005cc5] dark:**:[.hljs-section]:text-[#61a5f2] **:[.hljs-strong]:font-bold **:[.hljs-title,.hljs-title.class\\\\_,.hljs-title.class\\\\_.inherited\\\\_\\\\_,.hljs-title.function\\\\_]:text-[#6f42c1] dark:**:[.hljs-title,.hljs-title.class\\\\_,.hljs-title.class\\\\_.inherited\\\\_\\\\_,.hljs-title.function\\\\_]:text-[#a77bfa]"
      {...props}
    >
      <div className="relative rounded-md bg-muted/50">
        <pre className="overflow-x-auto p-8 pr-4 font-mono text-sm leading-[normal] [tab-size:2] print:break-inside-avoid">
          <code>{props.children}</code>
        </pre>
 
        <div
          className="absolute top-1 right-1 z-10 flex gap-0.5 select-none"
          contentEditable={false}
        >
          {element.language === 'json' && (
            <Button
              size="icon"
              variant="ghost"
              className="size-6 text-xs"
              onClick={() => {
                editor.plugin(BaseCodeBlockPlugin).update.format({ element });
              }}
              title="Format code"
            >
              <BracesIcon className="!size-3.5 text-muted-foreground" />
            </Button>
          )}
 
          <CodeBlockCombobox showLanguageLabel={showLanguageLabel} />
 
          <CopyButton
            size="icon"
            variant="ghost"
            className="size-6 gap-1 text-xs text-muted-foreground"
            value={() => NodeApi.string(element)}
          />
        </div>
      </div>
    </PlateElement>
  );
}
 
function CodeBlockCombobox({
  showLanguageLabel,
}: {
  showLanguageLabel: boolean;
}) {
  const [open, setOpen] = React.useState(false);
  const readOnly = useEditorReadOnly();
  const editor = useEditor();
  const element = useElement(CodeBlockPlugin);
  const value = element.language || 'plaintext';
  const [searchValue, setSearchValue] = React.useState('');
 
  const items = React.useMemo(
    () =>
      codeBlockLanguages.filter(
        (language) =>
          !searchValue ||
          language.label.toLowerCase().includes(searchValue.toLowerCase())
      ),
    [searchValue]
  );
 
  if (readOnly) {
    if (!showLanguageLabel) return null;
 
    return <CodeBlockLanguageLabel lang={element.language} />;
  }
 
  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger>
        <Button
          size="sm"
          variant="ghost"
          className="h-6 justify-between gap-1 px-2 text-xs text-muted-foreground select-none"
          aria-controls="code-block-language-options"
          aria-expanded={open}
          role="combobox"
        >
          {getCodeBlockLanguageLabel(value) ?? 'Plain Text'}
        </Button>
      </PopoverTrigger>
      <PopoverContent
        className="w-[200px] p-0"
        id="code-block-language-options"
        onFinalFocus={() => {
          setSearchValue('');
        }}
      >
        <Command shouldFilter={false}>
          <CommandInput
            className="h-9"
            value={searchValue}
            onValueChange={(innerValue) => {
              setSearchValue(innerValue);
            }}
            placeholder="Search language..."
          />
          <CommandEmpty>No language found.</CommandEmpty>
 
          <CommandList className="h-[344px] overflow-y-auto">
            <CommandGroup>
              {items.map((language) => (
                <CommandItem
                  key={language.label}
                  className="cursor-pointer"
                  value={language.value}
                  onSelect={(innerValue2) => {
                    const path = editor.read.nodes.path(element);
 
                    if (!path) return;
 
                    editor
                      .plugin(BaseCodeBlockPlugin)
                      .update.set({ language: innerValue2 }, { at: path });
                    setSearchValue(innerValue2);
                    setOpen(false);
                  }}
                >
                  <Check
                    className={cn(
                      value === language.value ? 'opacity-100' : 'opacity-0'
                    )}
                  />
                  {language.label}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}
 
function CodeBlockLanguageLabel({ lang }: { lang?: string | null }) {
  const label = getCodeBlockLanguageLabel(lang);
 
  if (!label) return null;
 
  return (
    <span className="flex h-6 items-center px-2 text-xs text-muted-foreground select-none">
      {label}
    </span>
  );
}
 
function CopyButton({
  value,
  ...props
}: { value: (() => string) | string } & Omit<
  React.ComponentProps<typeof Button>,
  'value'
>) {
  const [hasCopied, setHasCopied] = React.useState(false);
 
  React.useEffect(() => {
    if (!hasCopied) return undefined;
 
    const timeout = setTimeout(() => {
      setHasCopied(false);
    }, 2000);
 
    return () => {
      clearTimeout(timeout);
    };
  }, [hasCopied]);
 
  return (
    <Button
      onClick={() => {
        void navigator.clipboard.writeText(
          typeof value === 'function' ? value() : value
        );
        setHasCopied(true);
      }}
      {...props}
    >
      <span className="sr-only">Copy</span>
      {hasCopied ? (
        <CheckIcon className="!size-3" />
      ) : (
        <CopyIcon className="!size-3" />
      )}
    </Button>
  );
}
 
export function CodeLineElement(
  props: PlateElementProps<typeof CodeLinePlugin>
) {
  return <PlateElement {...props} />;
}
 
export function CodeSyntaxLeaf(
  props: PlateLeafProps<typeof CodeHighlightPlugin>
) {
  return <PlateLeaf className={props.leaf.className} {...props} />;
}
 
const lowlight = createLowlight(all);
 
export const CodeBlockKit = [
  CodeBlockPlugin.configure({
    component: CodeBlockElement,
    inputRules: [CodeBlockRules.markdown({ on: 'match' })],
    shortcuts: { toggle: { keys: 'mod+alt+8' } },
  }),
  CodeLinePlugin.configure({ component: CodeLineElement }),
  CodeHighlightPlugin.configure({
    component: CodeSyntaxLeaf,
    initialState: { lowlight },
  }),
];
'use client';
 
import { BaseCodeBlockPlugin, CodeBlockRules } from '@platejs/code-block';
import {
  CodeBlockPlugin,
  CodeHighlightPlugin,
  CodeLinePlugin,
} from '@platejs/code-block/react';
import { all, createLowlight } from 'lowlight';
import { BracesIcon, Check, CheckIcon, CopyIcon } from 'lucide-react';
import { NodeApi } from 'platejs';
import {
  PlateElement,
  PlateLeaf,
  type PlateElementProps,
  type PlateLeafProps,
  useEditor,
  useEditorReadOnly,
  useElement,
} from 'platejs/react';


























































































































































































































































































































































  • CodeBlockElement: Renders code block containers.
  • CodeLineElement: Renders individual code lines.
  • CodeSyntaxLeaf: Renders syntax highlighted text.

Add Kit

Add the kit to your plugins:

import { createPlateEditor } from 'platejs/react';
import { CodeBlockKit } from '@/components/editor/code-block';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    ...CodeBlockKit,
  ],
});
import { createPlateEditor } from 'platejs/react';
import { CodeBlockKit } from '@/components/editor/code-block';
 
const editor = createPlateEditor




Manual Usage

Installation

pnpm add @platejs/code-block lowlight
pnpm add @platejs/code-block lowlight

Add Plugins

Add CodeBlockPlugin for code-block editing. It installs CodeLinePlugin as a required dependency; add CodeHighlightPlugin separately when you want Lowlight syntax highlighting.

import { CodeBlockPlugin } from '@platejs/code-block/react';
import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    CodeBlockPlugin,
  ],
});
import { CodeBlockPlugin } from '@platejs/code-block/react';
import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({




Configure Plugins

Configure each capability where it is owned. CodeBlockPlugin owns editing, CodeLinePlugin owns line rendering, and CodeHighlightPlugin owns highlighting.

Basic Setup with All Languages:

import { CodeBlockRules } from '@platejs/code-block';
import { CodeBlockPlugin, CodeHighlightPlugin, CodeLinePlugin } from '@platejs/code-block/react';
import { all, createLowlight } from 'lowlight';
import { createPlateEditor } from 'platejs/react';
import { CodeBlockElement, CodeLineElement, CodeSyntaxLeaf } from '@/components/editor/code-block';
 
// Create a lowlight instance with all languages
const lowlight = createLowlight(all);
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    CodeBlockPlugin.configure({
      component: CodeBlockElement,
      inputRules: [CodeBlockRules.markdown({ on: 'match' })],








Custom Language Setup (Optimized Bundle):

For optimized bundle size, you can register only specific languages:

import { createLowlight } from 'lowlight';
import css from 'highlight.js/lib/languages/css';
import js from 'highlight.js/lib/languages/javascript';
import ts from 'highlight.js/lib/languages/typescript';
import html from 'highlight.js/lib/languages/xml';
 
// Create a lowlight instance
const lowlight = createLowlight();
 
// Register only the languages you need
lowlight.register('html', html);
lowlight.register('css', css);
lowlight.register('js', js);
lowlight.register('ts', ts);


















  • CodeBlockPlugin.configure({ component }): Assigns CodeBlockElement to render code block containers.
  • inputRules: Registers the triple-backtick fence rule. Use on: 'match' to commit when the fence becomes complete or on: 'break' to commit on Enter.
  • CodeHighlightPlugin.initialState.lowlight: Lowlight instance for syntax highlighting.
  • CodeHighlightPlugin.initialState.defaultLanguage: Default language when no language is specified.
  • shortcuts.toggle: Defines a keyboard shortcut to toggle code blocks.
  • component: Assigns components for code lines and syntax highlighting.

For plain code blocks, omit CodeHighlightPlugin:

import { CodeBlockPlugin } from '@platejs/code-block/react';
import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({
  plugins: [CodeBlockPlugin],
});
import { CodeBlockPlugin } from '@platejs/code-block/react';
import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({
  plugins: [CodeBlockPlugin],
});

For the runtime model, see Plugin Input Rules.

Turn Into Toolbar Button

You can add this item to the Turn Into Toolbar Button to convert blocks into code blocks:

import { PLUGINS } from 'platejs';
 
const turnIntoItem = {
  icon: <FileCodeIcon />,
  label: 'Code',
  value: PLUGINS.codeBlock,
};
import { PLUGINS } from 'platejs';
 
const turnIntoItem = {
  icon: <FileCodeIcon />,
  label: 'Code',
  value: PLUGINS.codeBlock,
};

Insert Toolbar Button

You can add this item to the Insert Toolbar Button to insert code block elements:

import { PLUGINS } from 'platejs';
 
const insertItem = {
  icon: <FileCodeIcon />,
  label: 'Code',
  value: PLUGINS.codeBlock,
};
import { PLUGINS } from 'platejs';
 
const insertItem = {
  icon: <FileCodeIcon />,
  label: 'Code',
  value: PLUGINS.codeBlock,
};

Plugins

CodeBlockPlugin

Owns the code-block schema, editing behavior, input rules, shortcuts, and commands. CodeLinePlugin is its required dependency.

CodeHighlightPlugin

Owns the codeSyntax mark, Lowlight decoration pipeline, and highlighted token rendering. Include or omit this descriptor as one ordinary plugin-array entry.

Options

    Default language to use when no language is specified. Set to null to disable syntax highlighting by default.

    Lowlight instance to use for highlighting. If not provided, syntax highlighting will be disabled.

CodeLinePlugin

Required structural dependency for code-block lines. Add it directly only to replace its default configuration or component.

Commands

Install CodeBlockPlugin, then mutate code blocks through its command group.

editor.update.codeBlock.insert();
editor.update.codeBlock.toggle();
editor.update.codeBlock.tab();
editor.update.codeBlock.untab();
editor.update.codeBlock.resetBlock();
editor.update.codeBlock.selectAll();
editor.update.codeBlock.insert();
editor.update.codeBlock.toggle();
editor.update.codeBlock.tab();
editor.update.codeBlock.untab();
editor.update.codeBlock.resetBlock();
editor.update.codeBlock.selectAll();
  • insert() converts an empty selected block in place. For non-empty or expanded selections, it inserts and selects a paragraph after the range-end block, then converts that paragraph. It is a no-op without a selection.
  • toggle() converts selected blocks to or from code blocks.
  • tab() and untab() indent or outdent selected code lines by two spaces.
  • resetBlock() unwraps the selected code block into paragraphs.
  • selectAll() selects the containing code block.

Queries

isCodeBlockEmpty

Returnsboolean

    Whether the selection is in an empty code block.

isSelectionAtCodeBlockStart

Returnsboolean

    Whether the selection is at the start of the first code line in a code block.

import * as React from 'react';
import { Button } from '@/components/ui/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import { cn } from '@/lib/utils';
import {
FloatingPopover as Popover,
FloatingPopoverContent as PopoverContent,
FloatingPopoverTrigger as PopoverTrigger,
} from '@/components/editor/floating-popover';
type CodeBlockElementProps = PlateElementProps<typeof CodeBlockPlugin> & {
showLanguageLabel?: boolean;
};
const codeBlockLanguages: Array<{ label: string; value: string }> = [
{ label: 'Auto', value: 'auto' },
{ label: 'Plain Text', value: 'plaintext' },
{ label: 'ABAP', value: 'abap' },
{ label: 'Agda', value: 'agda' },
{ label: 'Arduino', value: 'arduino' },
{ label: 'ASCII Art', value: 'ascii' },
{ label: 'Assembly', value: 'x86asm' },
{ label: 'Bash', value: 'bash' },
{ label: 'BASIC', value: 'basic' },
{ label: 'BNF', value: 'bnf' },
{ label: 'C', value: 'c' },
{ label: 'C#', value: 'csharp' },
{ label: 'C++', value: 'cpp' },
{ label: 'Clojure', value: 'clojure' },
{ label: 'CoffeeScript', value: 'coffeescript' },
{ label: 'Coq', value: 'coq' },
{ label: 'CSS', value: 'css' },
{ label: 'Dart', value: 'dart' },
{ label: 'Dhall', value: 'dhall' },
{ label: 'Diff', value: 'diff' },
{ label: 'Docker', value: 'dockerfile' },
{ label: 'EBNF', value: 'ebnf' },
{ label: 'Elixir', value: 'elixir' },
{ label: 'Elm', value: 'elm' },
{ label: 'Erlang', value: 'erlang' },
{ label: 'F#', value: 'fsharp' },
{ label: 'Flow', value: 'flow' },
{ label: 'Fortran', value: 'fortran' },
{ label: 'Gherkin', value: 'gherkin' },
{ label: 'GLSL', value: 'glsl' },
{ label: 'Go', value: 'go' },
{ label: 'GraphQL', value: 'graphql' },
{ label: 'Groovy', value: 'groovy' },
{ label: 'Haskell', value: 'haskell' },
{ label: 'HCL', value: 'hcl' },
{ label: 'HTML', value: 'html' },
{ label: 'Idris', value: 'idris' },
{ label: 'Java', value: 'java' },
{ label: 'JavaScript', value: 'javascript' },
{ label: 'JSON', value: 'json' },
{ label: 'Julia', value: 'julia' },
{ label: 'Kotlin', value: 'kotlin' },
{ label: 'LaTeX', value: 'latex' },
{ label: 'Less', value: 'less' },
{ label: 'Lisp', value: 'lisp' },
{ label: 'LiveScript', value: 'livescript' },
{ label: 'LLVM IR', value: 'llvm' },
{ label: 'Lua', value: 'lua' },
{ label: 'Makefile', value: 'makefile' },
{ label: 'Markdown', value: 'markdown' },
{ label: 'Markup', value: 'markup' },
{ label: 'MATLAB', value: 'matlab' },
{ label: 'Mathematica', value: 'mathematica' },
{ label: 'Mermaid', value: 'mermaid' },
{ label: 'Nix', value: 'nix' },
{ label: 'Notion Formula', value: 'notion' },
{ label: 'Objective-C', value: 'objectivec' },
{ label: 'OCaml', value: 'ocaml' },
{ label: 'Pascal', value: 'pascal' },
{ label: 'Perl', value: 'perl' },
{ label: 'PHP', value: 'php' },
{ label: 'PowerShell', value: 'powershell' },
{ label: 'Prolog', value: 'prolog' },
{ label: 'Protocol Buffers', value: 'protobuf' },
{ label: 'PureScript', value: 'purescript' },
{ label: 'Python', value: 'python' },
{ label: 'R', value: 'r' },
{ label: 'Racket', value: 'racket' },
{ label: 'Reason', value: 'reasonml' },
{ label: 'Ruby', value: 'ruby' },
{ label: 'Rust', value: 'rust' },
{ label: 'Sass', value: 'scss' },
{ label: 'Scala', value: 'scala' },
{ label: 'Scheme', value: 'scheme' },
{ label: 'SCSS', value: 'scss' },
{ label: 'Shell', value: 'shell' },
{ label: 'Smalltalk', value: 'smalltalk' },
{ label: 'Solidity', value: 'solidity' },
{ label: 'SQL', value: 'sql' },
{ label: 'Swift', value: 'swift' },
{ label: 'TOML', value: 'toml' },
{ label: 'TypeScript', value: 'typescript' },
{ label: 'VB.Net', value: 'vbnet' },
{ label: 'Verilog', value: 'verilog' },
{ label: 'VHDL', value: 'vhdl' },
{ label: 'Visual Basic', value: 'vbnet' },
{ label: 'WebAssembly', value: 'wasm' },
{ label: 'XML', value: 'xml' },
{ label: 'YAML', value: 'yaml' },
];
function getCodeBlockLanguageLabel(lang?: string | null) {
const value = lang?.trim();
if (!value) return null;
return (
codeBlockLanguages.find((language) => language.value === value)?.label ??
value
);
}
export function CodeBlockElement({
showLanguageLabel = true,
...props
}: CodeBlockElementProps) {
const { editor, element } = props;
return (
<PlateElement
className="py-1 **:[.hljs-addition]:bg-[#f0fff4] **:[.hljs-addition]:text-[#22863a] dark:**:[.hljs-addition]:bg-[#3c5743] dark:**:[.hljs-addition]:text-[#ceead5] **:[.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable]:text-[#005cc5] dark:**:[.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable]:text-[#6596cf] **:[.hljs-built\\\\_in,.hljs-symbol]:text-[#e36209] dark:**:[.hljs-built\\\\_in,.hljs-symbol]:text-[#c3854e] **:[.hljs-bullet]:text-[#735c0f] **:[.hljs-comment,.hljs-code,.hljs-formula]:text-[#6a737d] dark:**:[.hljs-comment,.hljs-code,.hljs-formula]:text-[#6a737d] **:[.hljs-deletion]:bg-[#ffeef0] **:[.hljs-deletion]:text-[#b31d28] dark:**:[.hljs-deletion]:bg-[#473235] dark:**:[.hljs-deletion]:text-[#e7c7cb] **:[.hljs-emphasis]:italic **:[.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\_]:text-[#d73a49] dark:**:[.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\_]:text-[#ee6960] **:[.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo]:text-[#22863a] dark:**:[.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo]:text-[#36a84f] **:[.hljs-regexp,.hljs-string,.hljs-meta_.hljs-string]:text-[#032f62] dark:**:[.hljs-regexp,.hljs-string,.hljs-meta_.hljs-string]:text-[#3593ff] **:[.hljs-section]:font-bold **:[.hljs-section]:text-[#005cc5] dark:**:[.hljs-section]:text-[#61a5f2] **:[.hljs-strong]:font-bold **:[.hljs-title,.hljs-title.class\\\\_,.hljs-title.class\\\\_.inherited\\\\_\\\\_,.hljs-title.function\\\\_]:text-[#6f42c1] dark:**:[.hljs-title,.hljs-title.class\\\\_,.hljs-title.class\\\\_.inherited\\\\_\\\\_,.hljs-title.function\\\\_]:text-[#a77bfa]"
{...props}
>
<div className="relative rounded-md bg-muted/50">
<pre className="overflow-x-auto p-8 pr-4 font-mono text-sm leading-[normal] [tab-size:2] print:break-inside-avoid">
<code>{props.children}</code>
</pre>
<div
className="absolute top-1 right-1 z-10 flex gap-0.5 select-none"
contentEditable={false}
>
{element.language === 'json' && (
<Button
size="icon"
variant="ghost"
className="size-6 text-xs"
onClick={() => {
editor.plugin(BaseCodeBlockPlugin).update.format({ element });
}}
title="Format code"
>
<BracesIcon className="!size-3.5 text-muted-foreground" />
</Button>
)}
<CodeBlockCombobox showLanguageLabel={showLanguageLabel} />
<CopyButton
size="icon"
variant="ghost"
className="size-6 gap-1 text-xs text-muted-foreground"
value={() => NodeApi.string(element)}
/>
</div>
</div>
</PlateElement>
);
}
function CodeBlockCombobox({
showLanguageLabel,
}: {
showLanguageLabel: boolean;
}) {
const [open, setOpen] = React.useState(false);
const readOnly = useEditorReadOnly();
const editor = useEditor();
const element = useElement(CodeBlockPlugin);
const value = element.language || 'plaintext';
const [searchValue, setSearchValue] = React.useState('');
const items = React.useMemo(
() =>
codeBlockLanguages.filter(
(language) =>
!searchValue ||
language.label.toLowerCase().includes(searchValue.toLowerCase())
),
[searchValue]
);
if (readOnly) {
if (!showLanguageLabel) return null;
return <CodeBlockLanguageLabel lang={element.language} />;
}
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger>
<Button
size="sm"
variant="ghost"
className="h-6 justify-between gap-1 px-2 text-xs text-muted-foreground select-none"
aria-controls="code-block-language-options"
aria-expanded={open}
role="combobox"
>
{getCodeBlockLanguageLabel(value) ?? 'Plain Text'}
</Button>
</PopoverTrigger>
<PopoverContent
className="w-[200px] p-0"
id="code-block-language-options"
onFinalFocus={() => {
setSearchValue('');
}}
>
<Command shouldFilter={false}>
<CommandInput
className="h-9"
value={searchValue}
onValueChange={(innerValue) => {
setSearchValue(innerValue);
}}
placeholder="Search language..."
/>
<CommandEmpty>No language found.</CommandEmpty>
<CommandList className="h-[344px] overflow-y-auto">
<CommandGroup>
{items.map((language) => (
<CommandItem
key={language.label}
className="cursor-pointer"
value={language.value}
onSelect={(innerValue2) => {
const path = editor.read.nodes.path(element);
if (!path) return;
editor
.plugin(BaseCodeBlockPlugin)
.update.set({ language: innerValue2 }, { at: path });
setSearchValue(innerValue2);
setOpen(false);
}}
>
<Check
className={cn(
value === language.value ? 'opacity-100' : 'opacity-0'
)}
/>
{language.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
function CodeBlockLanguageLabel({ lang }: { lang?: string | null }) {
const label = getCodeBlockLanguageLabel(lang);
if (!label) return null;
return (
<span className="flex h-6 items-center px-2 text-xs text-muted-foreground select-none">
{label}
</span>
);
}
function CopyButton({
value,
...props
}: { value: (() => string) | string } & Omit<
React.ComponentProps<typeof Button>,
'value'
>) {
const [hasCopied, setHasCopied] = React.useState(false);
React.useEffect(() => {
if (!hasCopied) return undefined;
const timeout = setTimeout(() => {
setHasCopied(false);
}, 2000);
return () => {
clearTimeout(timeout);
};
}, [hasCopied]);
return (
<Button
onClick={() => {
void navigator.clipboard.writeText(
typeof value === 'function' ? value() : value
);
setHasCopied(true);
}}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? (
<CheckIcon className="!size-3" />
) : (
<CopyIcon className="!size-3" />
)}
</Button>
);
}
export function CodeLineElement(
props: PlateElementProps<typeof CodeLinePlugin>
) {
return <PlateElement {...props} />;
}
export function CodeSyntaxLeaf(
props: PlateLeafProps<typeof CodeHighlightPlugin>
) {
return <PlateLeaf className={props.leaf.className} {...props} />;
}
const lowlight = createLowlight(all);
export const CodeBlockKit = [
CodeBlockPlugin.configure({
component: CodeBlockElement,
inputRules: [CodeBlockRules.markdown({ on: 'match' })],
shortcuts: { toggle: { keys: 'mod+alt+8' } },
}),
CodeLinePlugin.configure({ component: CodeLineElement }),
CodeHighlightPlugin.configure({
component: CodeSyntaxLeaf,
initialState: { lowlight },
}),
];
({
plugins: [
// ...otherPlugins,
...CodeBlockKit,
],
});
plugins: [
// ...otherPlugins,
CodeBlockPlugin,
],
});
shortcuts: { toggle: { keys: 'mod+alt+8' } },
}),
CodeLinePlugin.configure({ component: CodeLineElement }),
CodeHighlightPlugin.configure({
component: CodeSyntaxLeaf,
initialState: { lowlight },
}),
],
});
import { CodeBlockRules } from '@platejs/code-block';
import { CodeBlockPlugin, CodeHighlightPlugin, CodeLinePlugin } from '@platejs/code-block/react';
import { all, createLowlight } from 'lowlight';
import { createPlateEditor } from 'platejs/react';
import { CodeBlockElement, CodeLineElement, CodeSyntaxLeaf } from '@/components/editor/code-block';
 
// Create a lowlight instance with all languages
const lowlight = createLowlight(all);
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    CodeBlockPlugin.configure({
      component: CodeBlockElement,
      inputRules: [CodeBlockRules.markdown({ on: 'match' })],
      shortcuts: { toggle: { keys: 'mod+alt+8' } },
    }),
    CodeLinePlugin.configure({ component: CodeLineElement }),
    CodeHighlightPlugin.configure({
      component: CodeSyntaxLeaf,
      initialState: { lowlight },
    }),
  ],
});
const editor = createPlateEditor({
plugins: [
// ...otherPlugins,
CodeBlockPlugin.configure({
component: CodeBlockElement,
inputRules: [CodeBlockRules.markdown({ on: 'match' })],
shortcuts: { toggle: { keys: 'mod+alt+8' } },
}),
CodeLinePlugin.configure({ component: CodeLineElement }),
CodeHighlightPlugin.configure({
component: CodeSyntaxLeaf,
initialState: {
lowlight,
defaultLanguage: 'js', // Set default language (optional)
},
}),
],
});
import { createLowlight } from 'lowlight';
import css from 'highlight.js/lib/languages/css';
import js from 'highlight.js/lib/languages/javascript';
import ts from 'highlight.js/lib/languages/typescript';
import html from 'highlight.js/lib/languages/xml';
 
// Create a lowlight instance
const lowlight = createLowlight();
 
// Register only the languages you need
lowlight.register('html', html);
lowlight.register('css', css);
lowlight.register('js', js);
lowlight.register('ts', ts);
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    CodeBlockPlugin.configure({
      component: CodeBlockElement,
      inputRules: [CodeBlockRules.markdown({ on: 'match' })],
      shortcuts: { toggle: { keys: 'mod+alt+8' } },
    }),
    CodeLinePlugin.configure({ component: CodeLineElement }),
    CodeHighlightPlugin.configure({
      component: CodeSyntaxLeaf,
      initialState: {
        lowlight,
        defaultLanguage: 'js', // Set default language (optional)
      },
    }),
  ],
});