Image

Images fit the width of the content area. Click one to select it, then drag the handle in its corner to resize it.

Installation

This pattern is built from files in @intentui/editor. Install the editor, then copy the code above into a component of your own.

npx shadcn@latest add @intentui/editor

What's included

PieceWhy it is here
RichTextExtensionLexical's rich text behavior. Every pattern needs it.
InitialStateExtensionApplies the seeded content immediately, so the server and the client render the same. Leave it out if your editor starts empty. See Seeding initial content.
ImageExtensionRegisters ImageNode and the INSERT_IMAGE_COMMAND handler. Remove it and every other piece fails.
DragDropPasteExtensionInserts images that are pasted or dropped into the editor. Without it only the toolbar and slash menu add images.
registerMarkdownShortcuts(editor, [IMAGE])Converts ![alt](url) as soon as you type the closing parenthesis.
InsertImagePluginThe toolbar button that opens a file picker.
ImagePickerPluginThe "Insert image" item in the slash menu. It also renders the hidden file input.
ComponentPicker, BlockInsert, ToolbarContainers for the slash menu item and the button.

Adding images

MethodHow
ToolbarThe "Insert image" button opens a file picker.
Slash menuType /, then choose "Insert image" (keywords: image, photo, picture, file, img).
Paste or dropPaste or drop image files into the content.
MarkdownType ![alt text](https://example.com/photo.png).

The file picker accepts image/* and inserts one file at a time. Paste and drop accept any image/ type, including GIF, WebP, HEIC and HEIF, and insert every image file.

Where the file goes

The editor does not upload anything. Picked, pasted and dropped files are read in the browser with FileReader.readAsDataURL, and the data URL is stored as the image src with the file name as the alt text. Images therefore live inside the editor content itself, which makes large images expensive to save and export.

To upload instead, replace the data URL with your own URL before the image is inserted. There are two places to change:

  • useImageFilePicker in plugins/block-insert/insert-image-plugin.tsx: onChange calls readFileAsDataUrl(file) and passes the result as src.
  • extensions/drag-drop-paste-extension.ts: the handler reads files with mediaFileReader and dispatches INSERT_IMAGE_COMMAND with src: result.
extensions/drag-drop-paste-extension.ts
editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
  altText: file.name,
  src: await uploadFile(file), // your function, returns a URL
});

INSERT_IMAGE_COMMAND accepts altText and src (required), and optional width, height and maxWidth.

Selecting and resizing

  • Click an image to select it. It gets a 2px primary outline. Shift click toggles the selection.
  • A round handle at the bottom corner appears when you hover the image. Drag it to resize. The aspect ratio is kept, the width is clamped between 100 and maxWidth (500 by default), and the size is saved on the image.
  • The handle is not shown in read-only mode.
  • If the src fails to load, a dashed placeholder with an icon and the alt text is shown instead.

Styling

The image is styled in nodes/image-node.tsx, not in theme.ts. The wrapper is group/image relative inline-block max-w-full align-top leading-none, the image is block h-auto max-w-full rounded-lg select-none, and the selection outline is outline-2 outline-offset-2 outline-primary. The default width cap is maxWidth = 500 in $createImageNode.