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
| Piece | Why it is here |
|---|---|
RichTextExtension | Lexical's rich text behavior. Every pattern needs it. |
InitialStateExtension | Applies 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. |
ImageExtension | Registers ImageNode and the INSERT_IMAGE_COMMAND handler. Remove it and every other piece fails. |
DragDropPasteExtension | Inserts images that are pasted or dropped into the editor. Without it only the toolbar and slash menu add images. |
registerMarkdownShortcuts(editor, [IMAGE]) | Converts  as soon as you type the closing parenthesis. |
InsertImagePlugin | The toolbar button that opens a file picker. |
ImagePickerPlugin | The "Insert image" item in the slash menu. It also renders the hidden file input. |
ComponentPicker, BlockInsert, Toolbar | Containers for the slash menu item and the button. |
Adding images
| Method | How |
|---|---|
| Toolbar | The "Insert image" button opens a file picker. |
| Slash menu | Type /, then choose "Insert image" (keywords: image, photo, picture, file, img). |
| Paste or drop | Paste or drop image files into the content. |
| Markdown | Type . |
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:
useImageFilePickerinplugins/block-insert/insert-image-plugin.tsx:onChangecallsreadFileAsDataUrl(file)and passes the result assrc.extensions/drag-drop-paste-extension.ts: the handler reads files withmediaFileReaderand dispatchesINSERT_IMAGE_COMMANDwithsrc: result.
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
2pxprimary 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
100andmaxWidth(500by default), and the size is saved on the image. - The handle is not shown in read-only mode.
- If the
srcfails 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.
Related patterns
- Slash menu for the menu that lists "Insert image".
- Insert block menu for the toolbar group.
- Import and export to save images as Markdown.