Claude
Skills
Sign in
Back

markdown-editor-integrator

Included with Lifetime
$97 forever

This skill should be used when installing and configuring markdown editor functionality using @uiw/react-md-editor. Applies when adding rich text editing, markdown support, WYSIWYG editors, content editing with preview, or text formatting features. Trigger terms include markdown editor, rich text editor, text editor, add markdown, install markdown editor, markdown component, WYSIWYG, content editor, text formatting, editor preview.

Designassets

What this skill does


# Markdown Editor Integrator

Install and configure @uiw/react-md-editor with theme integration, server-side sanitization, controlled/uncontrolled modes, and proper persistence for worldbuilding content.

## When to Use This Skill

Apply this skill when:
- Adding markdown editing capability to forms
- Creating rich text editing for entity descriptions
- Building content management features
- Adding WYSIWYG editing with markdown preview
- Implementing text formatting for character bios, location descriptions, lore entries
- Setting up markdown support for notes and documentation
- Creating editing interfaces for narrative content

## Overview

@uiw/react-md-editor is a React markdown editor with:
- Live preview with split/edit/preview modes
- Syntax highlighting
- Markdown shortcuts and toolbar
- Theme customization
- No SSR issues
- TypeScript support

## Installation Process

### Step 1: Install Dependencies

```bash
npm install @uiw/react-md-editor
```

For sanitization (security):
```bash
npm install rehype-sanitize
```

### Step 2: Configure Next.js (if using)

Add to next.config.js to avoid SSR issues:

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Other config...
  webpack: (config) => {
    config.resolve.alias = {
      ...config.resolve.alias,
      '@uiw/react-md-editor': '@uiw/react-md-editor',
    }
    return config
  },
}

module.exports = nextConfig
```

### Step 3: Create Editor Component

Create wrapper component at `components/MarkdownEditor.tsx`:

See assets/MarkdownEditor.tsx for full implementation.

### Step 4: Create Preview Component

Create preview component at `components/MarkdownPreview.tsx`:

See assets/MarkdownPreview.tsx for full implementation.

### Step 5: Integrate Theme Styling

Configure editor to match shadcn/ui theme:

See references/theme-integration.md for detailed theming.

### Step 6: Add Server-Side Sanitization

Implement sanitization for security:

See references/sanitization.md for implementation details.

## Basic Usage Patterns

### Controlled Mode (Recommended for Forms)

```tsx
'use client'

import { useState } from 'react'
import { MarkdownEditor } from '@/components/MarkdownEditor'
import { Button } from '@/components/ui/button'

export function CharacterBioForm() {
  const [bio, setBio] = useState('')

  async function handleSubmit() {
    await saveCharacter({ bio })
  }

  return (
    <div className="space-y-4">
      <div>
        <label className="text-sm font-medium">Biography</label>
        <MarkdownEditor
          value={bio}
          onChange={(value) => setBio(value || '')}
          height={400}
        />
      </div>
      <Button onClick={handleSubmit}>Save</Button>
    </div>
  )
}
```

### With React Hook Form

```tsx
'use client'

import { useForm, Controller } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { MarkdownEditor } from '@/components/MarkdownEditor'
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'

const schema = z.object({
  description: z.string().min(1, 'Description required').max(10000)
})

type FormValues = z.infer<typeof schema>

export function LocationForm() {
  const form = useForm<FormValues>({
    resolver: zodResolver(schema),
    defaultValues: {
      description: ''
    }
  })

  function onSubmit(values: FormValues) {
    console.log(values)
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
        <FormField
          control={form.control}
          name="description"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Description</FormLabel>
              <FormControl>
                <MarkdownEditor
                  value={field.value}
                  onChange={(value) => field.onChange(value || '')}
                  height={400}
                />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Submit</Button>
      </form>
    </Form>
  )
}
```

### Preview Mode (Display Only)

```tsx
import { MarkdownPreview } from '@/components/MarkdownPreview'

export function CharacterProfile({ character }) {
  return (
    <div>
      <h2>{character.name}</h2>
      <div className="prose dark:prose-invert max-w-none">
        <MarkdownPreview content={character.biography} />
      </div>
    </div>
  )
}
```

### Uncontrolled Mode

```tsx
'use client'

import { useRef } from 'react'
import MDEditor from '@uiw/react-md-editor'

export function QuickNoteEditor() {
  const editorRef = useRef<HTMLDivElement>(null)

  function handleSave() {
    // Access value from ref if needed
  }

  return (
    <MDEditor
      ref={editorRef}
      defaultValue="Initial content"
      height={300}
    />
  )
}
```

## Configuration Options

### Height Control

```tsx
// Fixed height
<MarkdownEditor height={400} />

// Dynamic height
<MarkdownEditor height="60vh" />

// Auto height
<MarkdownEditor height="auto" />
```

### Hide Toolbar

```tsx
<MarkdownEditor
  hideToolbar
  value={value}
  onChange={setValue}
/>
```

### Preview Mode

```tsx
<MarkdownEditor
  preview="edit"    // Edit only
  preview="live"    // Split view (default)
  preview="preview" // Preview only
  value={value}
  onChange={setValue}
/>
```

### Disable Preview

```tsx
<MarkdownEditor
  enablePreview={false}
  value={value}
  onChange={setValue}
/>
```

### Custom Commands

```tsx
import { commands } from '@uiw/react-md-editor'

<MarkdownEditor
  commands={[
    commands.bold,
    commands.italic,
    commands.strikethrough,
    commands.hr,
    commands.divider,
    commands.link,
    commands.quote,
    commands.code,
    commands.image,
    commands.unorderedListCommand,
    commands.orderedListCommand,
    commands.checkedListCommand,
  ]}
  value={value}
  onChange={setValue}
/>
```

### Extra Commands

```tsx
<MarkdownEditor
  extraCommands={[
    commands.codeEdit,
    commands.codeLive,
    commands.codePreview,
    commands.divider,
    commands.fullscreen,
  ]}
  value={value}
  onChange={setValue}
/>
```

## Theme Integration

### Match shadcn/ui Theme

```tsx
'use client'

import { useTheme } from 'next-themes'
import MDEditor from '@uiw/react-md-editor'

export function ThemedMarkdownEditor({ value, onChange }) {
  const { theme } = useTheme()

  return (
    <div data-color-mode={theme === 'dark' ? 'dark' : 'light'}>
      <MDEditor
        value={value}
        onChange={onChange}
        height={400}
        className="rounded-md border"
      />
    </div>
  )
}
```

### Custom Styling

```css
/* globals.css or component CSS */

.w-md-editor {
  @apply rounded-md border border-input bg-background;
}

.w-md-editor-toolbar {
  @apply border-b border-border bg-muted/50;
}

.w-md-editor-toolbar button {
  @apply text-foreground hover:bg-accent hover:text-accent-foreground;
}

.w-md-editor-content {
  @apply text-foreground;
}

.w-md-editor-preview {
  @apply prose prose-sm dark:prose-invert max-w-none;
}

.wmde-markdown {
  @apply bg-background text-foreground;
}

/* Code blocks */
.w-md-editor-preview pre {
  @apply bg-muted;
}

.w-md-editor-preview code {
  @apply text-primary;
}
```

## Sanitization for Security

### Client-Side Sanitization

```tsx
import MDEditor from '@uiw/react-md-editor'
import rehypeSanitize from 'rehype-sanitize'

<MarkdownEditor
  value={value}
  onChange={onChange}
  previewOptions={{
    rehypePlugins: [[rehypeSanitize]],
  }}
/>
```

### Server-Side Sanitization

```typescript
// lib/sanitize-markdown.ts
import { remark } from 'remark'
import remarkHtml from 'remark-html'
import { sanitize } from 'isomorphic-dompurify'

export async function sanitizeMarkdown(markdown: string): Promise<string> {
  // Convert markdown to HTML
  const result = await remark()
    .use(remarkHtml)
    .process(markdown)

  const html = res

Related in Design