---
title: "Rich Text — Lexical Views"
aliases: [lexical-views, rich-text-views, lexical-node-views]
tags: [payloadcms, lexical, rich-text, views, admin-panel, jsx-converters]
sources: [raw/rich-text__views.md]
created: 2026-05-15
updated: 2026-05-15
---

## Overview

> **Experimental** — may change or be removed in future releases.

Views let you define **named rendering modes** for Lexical nodes that work in both the admin panel and JSX converters. An editor can switch between views (e.g. `default`, `preview`, `branded`) using a built-in selector above the field, without changing the underlying data structure.

**When to use views instead of plain JSX converters:**
- You want to share the same node rendering logic between the admin panel and frontend
- You need multiple rendering modes for the same field
- You want a site-preview experience inside the admin panel (A/B testing, theming)

If you only need frontend rendering and don't need admin panel integration, [[wiki/payloadcms/rich-text-converting-jsx|JSX converters]] alone are simpler.

---

## Defining Views

### Step 1 — Create a views file (client component)

```tsx
// collections/Posts/views.tsx
'use client'
import type { LexicalEditorViewMap } from '@payloadcms/richtext-lexical'

export const postViews: LexicalEditorViewMap = {
  default: {
    nodes: {
      heading: {
        createDOM({ node }) {
          const el = document.createElement(node.getTag())
          el.style.color = '#333'
          return el
        },
      },
    },
  },
  preview: {
    admin: { hideGutter: true },
    lexical: (defaultConfig) => ({
      ...defaultConfig,
      theme: { ...defaultConfig.theme, link: 'preview-link' },
    }),
    nodes: {
      heading: {
        createDOM({ node }) {
          const el = document.createElement(node.getTag())
          el.style.color = '#3b82f6'
          return el
        },
      },
      blocks: {
        myBlock: {
          Component: ({ node, isEditor }) => {
            const text = isEditor ? node.__fields?.text : node.fields?.text
            return <div style={{ background: 'linear-gradient(135deg,#667eea,#764ba2)', color:'white', padding:'24px' }}>{text}</div>
          },
        },
      },
    },
  },
}
```

### Step 2 — Reference views in the field config

```ts
// collections/Posts/index.ts
editor: lexicalEditor({
  views: './views#postViews',
})
```

The value is an **import path** (same format as [[wiki/payloadcms/custom-components-authoring|custom component paths]]).

---

## View Configuration Options

Each named view can override three aspects:

| Key | What it controls |
|-----|-----------------|
| `admin` | UI options: `hideGutter`, `hideAddBlockButton`, `placeholder`, etc. — same options as the top-level `lexicalEditor()` admin config |
| `lexical` | Lexical editor config (theme classes etc.) — pass a function `(defaultConfig) => config` to extend, or a plain object to replace |
| `nodes` | Per-node-type rendering overrides |
| `filterFeatures` | Remove/add client features per view (e.g. strip toolbars for a clean preview) |

### filterFeatures example

```tsx
preview: {
  filterFeatures: (features) => {
    const { toolbarFixed, toolbarInline, ...rest } = features
    return rest
  },
}
```

---

## Node Override Approaches

Three approaches, per node:

### `Component` — React component (admin + JSX converters)

```tsx
nodes: {
  myNode: {
    Component: ({ node, isEditor, isJSXConverter }) => {
      const data = isEditor ? node.__fields : node.fields
      return <div>{data.text}</div>
    },
  },
}
```

- `isEditor: true` → rendering in admin panel; access via `node.__fields`
- `isJSXConverter: true` → rendering for frontend; access via `node.fields`

### `createDOM` — native DOM (admin panel only)

```tsx
nodes: {
  heading: {
    createDOM({ node }) {
      const el = document.createElement(node.getTag())
      el.style.color = '#3b82f6'
      return el
    },
  },
}
```

Only works in the editor (ElementNodes). Not used in JSX converters.

### `html` — raw HTML string (admin + JSX converters)

```tsx
nodes: {
  link: {
    html: ({ node, isEditor }) => {
      const url = isEditor ? node.__url : node.url
      return `<a href="${url}">Click here</a>`
    },
  },
}
```

**Precedence when both provided:** `createDOM` wins in admin panel; `html` is used in JSX converters.

---

## Node Types Reference

```
nodes:
  heading / paragraph / link / list / listitem / quote / horizontalrule
  blocks:
    <blockSlug>: { Component | Block | Label }
  inlineBlocks:
    <blockSlug>: { Component }
```

### Block-specific keys

| Key | When to use |
|-----|-------------|
| `Component` | Full render control — works in admin + JSX converters |
| `Block` | Customize appearance while keeping default form (collapsible, edit/remove buttons) — admin only |
| `Label` | Replace only the collapsible header label — admin only, no effect in JSX converters |

**Bundle size warning:** If the view's node map is also passed to a JSX converter, do **not** import from `@payloadcms/richtext-lexical/client` or `@payloadcms/ui` at the top level — those imports will be bundled into the frontend. Guard editor-only code with `if (props.isEditor)`.

---

## Admin Panel Integration

When views are defined, a **view selector** appears automatically above the field. Users switch between views without changing data.

### `useRichTextView` hook

```tsx
'use client'
import { useRichTextView } from '@payloadcms/richtext-lexical/client'

const { currentView, views, currentViewMap } = useRichTextView()
```

---

## Using View Node Maps with JSX Converters

Pass a view's `nodes` to `RichText` or `convertLexicalToJSX` for identical rendering on the frontend:

```tsx
import { RichText } from '@payloadcms/richtext-lexical/react'
import { postViews } from './views'

export function BlogPost({ post }) {
  return <RichText data={post.content} nodeMap={postViews.preview.nodes} />
}
```

**Precedence when both `converters` and `nodeMap` are provided:**
- Node map wins per-node-type
- For blocks/inline blocks: deep-merged; node map wins on conflicts

---

## Key Takeaways

- Views are **experimental** — API may change
- A view is a named set of `{ nodes, admin, lexical, filterFeatures }` overrides; the `default` view is loaded first
- The **admin panel** shows a built-in view selector; data structure is untouched when switching views
- The same `nodes` map can be passed to `RichText`/`convertLexicalToJSX` via `nodeMap` for identical frontend rendering
- Three node override approaches: `Component` (works everywhere), `createDOM` (admin only), `html` (works everywhere)
- Always guard editor-only imports (`@payloadcms/richtext-lexical/client`) with `isEditor` checks to avoid bloating the frontend bundle

---

## Related

- [[wiki/payloadcms/rich-text|Rich Text (Lexical) — Overview]]
- [[wiki/payloadcms/rich-text-converting-jsx|Rich Text — Converting to JSX]]
- [[wiki/payloadcms/rich-text-blocks|Rich Text — Blocks (BlocksFeature)]]
- [[wiki/payloadcms/rich-text-custom-features|Rich Text — Custom Lexical Features]]
- [[wiki/payloadcms/custom-components-authoring|Custom Components — Authoring Guide]]

---

## Sources

- `raw/rich-text__views.md`
- https://payloadcms.com/docs/rich-text/views
