# Minimal editor

> Adjust, filters and crop, plus export. Nothing else.

A shadcn/ui block from the [@photocn](https://photocn.dev) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/photocn/editor-minimal).

- Page, with a live preview: https://shadcn.exchange/r/photocn/editor-minimal
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/photocn/editor-minimal-light-74d6add192bf91fc.png
- Registry item: https://photocn.dev/r/editor-minimal.json
- Categories: blocks
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://photocn.dev/r/editor-minimal.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`, `photocn`
- Registry items: `https://photocn.dev/r/image-editor-adjustments.json`, `https://photocn.dev/r/image-editor-canvas.json`, `https://photocn.dev/r/image-editor-crop.json`, `https://photocn.dev/r/image-editor-export.json`, `https://photocn.dev/r/image-editor-filters.json`, `https://photocn.dev/r/image-editor-toolbar.json`

## Files

### `src/registry/blocks/editor-minimal.tsx`

```tsx
"use client";

import { CropIcon, SlidersHorizontalIcon, WandSparklesIcon } from "lucide-react";
import { ImageEditorProvider, useImageEditor } from "photocn/react";

import { ImageEditorAdjustments } from "@/components/image-editor/adjustments-panel";
import { ImageEditorCanvas } from "@/components/image-editor/canvas";
import { ImageEditorCrop } from "@/components/image-editor/crop-panel";
import { ImageEditorExportDialog } from "@/components/image-editor/export-dialog";
import { ImageEditorFilters } from "@/components/image-editor/filters-panel";
import { ImageEditorRedoButton, ImageEditorUndoButton } from "@/components/image-editor/toolbar";
import { cn } from "@/lib/utils";

const tabs = [
  { value: "adjust", label: "Adjust", icon: SlidersHorizontalIcon },
  { value: "filters", label: "Filters", icon: WandSparklesIcon },
  { value: "compose", label: "Crop", icon: CropIcon },
] as const;

/** Three tools and export: the smallest useful editor. */
export function EditorMinimalBlock({ src }: { src?: string | File | Blob }) {
  return (
    <ImageEditorProvider src={src}>
      <div className="flex h-dvh flex-col bg-background">
        <header className="flex items-center gap-1 border-b px-2 py-2">
          <ImageEditorUndoButton />
          <ImageEditorRedoButton />
          <Tabs />
          <div className="ml-auto">
            <ImageEditorExportDialog />
          </div>
        </header>
        <div className="grid min-h-0 flex-1 grid-rows-[1fr_auto] md:grid-cols-[1fr_320px] md:grid-rows-1">
          <ImageEditorCanvas className="min-h-0 p-2" />
          <aside className="max-h-[45dvh] overflow-y-auto border-t md:max-h-none md:border-t-0 md:border-l">
            <Panel />
          </aside>
        </div>
      </div>
    </ImageEditorProvider>
  );
}

function Tabs() {
  const editor = useImageEditor();
  return (
    <nav aria-label="Tools" className="mx-auto flex rounded-lg bg-muted p-0.5">
      {tabs.map((tab) => (
        <button
          aria-pressed={editor.tool === tab.value}
          className={cn(
            "flex min-h-9 items-center gap-1.5 rounded-md px-3 text-sm text-muted-foreground transition-colors",
            editor.tool === tab.value && "bg-background text-foreground shadow-sm",
          )}
          key={tab.value}
          onClick={() => editor.setTool(tab.value)}
          type="button"
        >
          <tab.icon className="size-4" />
          <span className="hidden sm:inline">{tab.label}</span>
        </button>
      ))}
    </nav>
  );
}

function Panel() {
  const { tool } = useImageEditor();
  if (tool === "filters") return <ImageEditorFilters />;
  if (tool === "compose") return <ImageEditorCrop />;
  return <ImageEditorAdjustments />;
}
```
