# @wix/ricos

Ricos is the Rich Content solution for Wix — used both internally and in headless environments. It provides three core building blocks that can be used together or independently:

- **RicosEditor** — a full-featured WYSIWYG editor with an extensible plugin system
- **RicosViewer** — a read-only renderer for displaying rich content
- **RicosDocument** — the content data model (JSON) that flows between editor and viewer.

<svg viewBox="0 0 620 260" xmlns="http://www.w3.org/2000/svg" style="max-width:620px;margin:24px auto;display:block">
  <style>
    .box { fill: #f8fafc; stroke: #94a3b8; stroke-width: 2; rx: 12; }
    .ellipse { fill: #f8fafc; stroke: #94a3b8; stroke-width: 2; }
    .label { font-family: ui-monospace, monospace; font-size: 18px; fill: #1e293b; text-anchor: middle; dominant-baseline: central; }
    .arrow { fill: none; stroke: #94a3b8; stroke-width: 1.5; marker-end: url(#arrowhead); }
  </style>
  <defs><marker id="arrowhead" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#94a3b8"/></marker></defs>
  <rect class="box" x="20" y="120" width="200" height="100"/>
  <text class="label" x="120" y="170">RicosEditor</text>
  <rect class="box" x="400" y="120" width="200" height="100"/>
  <text class="label" x="500" y="170">RicosViewer</text>
  <ellipse class="ellipse" cx="310" cy="55" rx="120" ry="40"/>
  <text class="label" x="310" y="55">RicosDocument</text>
  <line class="arrow" x1="200" y1="130" x2="260" y2="85"/>
  <line class="arrow" x1="270" y1="95" x2="215" y2="135"/>
  <line class="arrow" x1="350" y1="85" x2="420" y2="130"/>
</svg>

```bash
npm i @wix/ricos
```

All three are exported from `@wix/ricos`:

```tsx
import { RicosEditor, RicosViewer, type RicosDocument } from '@wix/ricos';
```

## Quick Start

### Viewer

```tsx widget
import '@wix/ricos/css/plugin-image-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RicosDocument } from '@wix/ricos';
import { pluginImageViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginImageViewer()]} />;
  // @essential-end
};

const content: RicosDocument = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'p1',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          nodes: [],
          textData: { text: 'A read-only Ricos document with an image:', decorations: [] },
        },
      ],
    },
    {
      type: 'IMAGE',
      id: 'img1',
      nodes: [],
      imageData: {
        containerData: {
          width: { size: 'CONTENT' },
          alignment: 'CENTER',
          textWrap: true,
        },
        image: {
          src: { url: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_1280.jpg' },
          width: 5120,
          height: 3413,
        },
        altText: 'A tree during sunset.',
      },
    },
  ],
};
```

### Editor

```tsx widget
import type { RicosDocument, RicosEditorType } from '@wix/ricos';
import { pluginImage, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  const editorRef = React.useRef<RicosEditorType>(null);

  // oxlint-disable-next-line no-alert
  const handlePublish = async () => {
    // @essential-start
    const document = await editorRef.current?.prePublish();
    alert(`Publishing ${document?.nodes.length ?? 0} nodes`);
    // @essential-end
  };

  return (
    // @essential-start
    <>
      <RicosEditor
        ref={editorRef}
        initialContent={initialContent}
        plugins={[pluginImage()]}
        placeholder={'Edit me — drag an image in or use the toolbar'}
      />
      <button onClick={handlePublish} type="button">
        Publish
      </button>
    </>
    // @essential-end
  );
};

const initialContent: RicosDocument = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'p1',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          nodes: [],
          textData: { text: 'An editable Ricos document with an image.', decorations: [] },
        },
      ],
    },
    {
      type: 'IMAGE',
      id: 'img1',
      nodes: [],
      imageData: {
        containerData: {
          width: { size: 'CONTENT' },
          alignment: 'CENTER',
          textWrap: true,
        },
        image: {
          src: { url: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_1280.jpg' },
          width: 5120,
          height: 3413,
        },
        altText: 'A tree during sunset.',
      },
    },
  ],
};
```


### Basic Setup

`RicosEditor` needs an authenticated context to load plugins, upload media, and resolve relative links. Pick the variant matching your hosting environment.

#### Headless: pass a `wixClient`

When you own the React tree and have an `@wix/sdk` client, pass it directly. The editor uses it for every authenticated request:

```tsx widget
import { createClient, OAuthStrategy } from '@wix/sdk';
import { pluginImage, RicosEditor } from '@wix/ricos';
import React from 'react';

const wixClient = createClient({
  modules: {},
  auth: OAuthStrategy({ clientId: 'YOUR_CLIENT_ID' }),
});

export default () => (
  // @essential-start
  <RicosEditor
    wixClient={wixClient}
    baseUrl="https://www.example.com/my-site"
    plugins={[pluginImage()]}
    placeholder="Type or insert an image…"
  />
  // @essential-end
);
```

#### In-Wix: pass an `instance` (and `msid`)

When the editor renders inside a Wix-served page, your runtime usually already has a per-session `instance` token and the site's `msid`. Pass them and Ricos builds its own client internally:

```tsx widget
import { pluginImage, RicosEditor } from '@wix/ricos';
import React from 'react';

export default ({ instance, msid }: { instance: string; msid: string }) => (
  // @essential-start
  <RicosEditor
    instance={instance}
    msid={msid}
    baseUrl="https://www.example.com/my-site"
    plugins={[pluginImage()]}
    placeholder="Type or insert an image…"
  />
  // @essential-end
);
```

#### Always pass `baseUrl`

`baseUrl` is the public URL of the site that owns the content. It's used by link decorations, button plugins, gallery items, and vertical embeds to resolve page-relative hrefs (`/about` → `https://www.example.com/my-site/about`).

The in-Wix flow can auto-fetch this from `/_api/ricos-site-pages/base-url`, but in any headless or cross-origin context you must pass it explicitly — without it, page-relative links in your content render as raw paths.

### Ricos Editor Props

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosEditorProps</span>

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosEditorProps</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>plugins</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#editorpluginoptions"><span class="font-mono">EditorPluginOptions</span></a>[]</code></dt>
<dd><p>Array of editor plugins to enable. Each plugin adds editing capabilities (e.g., image, video, table).</p></dd>
<dt><code><strong>onChange</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#oncontentchangefunction"><span class="font-mono">OnContentChangeFunction</span></a></code></dt>
<dd><p>Fires only when document content changes (not on selection changes).</p></dd>
<dt><code><strong>onSelectionChange</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#onselectionchangefunction"><span class="font-mono">OnSelectionChangeFunction</span></a></code></dt>
<dd><p>Fires when the selection/cursor position changes (including during edits).</p></dd>
<dt><code><strong>placeholder</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Placeholder text displayed when the editor is empty.</p></dd>
</dl>

<div class="pl-4 border-l-2 border-slate-200 mb-4">

```tsx widget
import { RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosEditor placeholder={'Type here!'} />;
  // @essential-end
};
```

</div>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>toolbars</strong><span class="text-slate-400">?</span>: <a href="#toolbarsconfig"><span class="font-mono">ToolbarsConfig</span></a></code></dt>
<dd><p>Toolbars configuration — controls the text formatting toolbar, insert plugin toolbars, plugin context toolbars and the quick-actions menu.</p></dd>
<dt><code><strong>onBusyChange</strong><span class="text-slate-400">?</span>: (isBusy: boolean) =&gt; void</code></dt>
<dd><p>Callback fired when the editor transitions between busy and idle states (e.g., during media uploads).</p></dd>
<dt><code><strong>maxTextLength</strong><span class="text-slate-400">?</span>: number</code></dt>
<dd><p>Maximum number of text characters allowed in the editor. When the limit is reached, further text input is ignored and paste actions are canceled if the result would exceed the limit.</p></dd>
<dt><code><strong>sideBlockComponent</strong><span class="text-slate-400">?</span>: FC&lt;{         id: string;     }&gt;</code></dt>
<dd><p>A React component rendered as a side block alongside the editor content. Receives the block `id` as a prop.</p></dd>
<dt><code><strong>commands</strong><span class="text-slate-400">?</span>: any[]</code></dt>
<dd><p>Custom commands to register with the editor.</p></dd>
<dt><code><strong>historySettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#historysettings"><span class="font-mono">HistorySettings</span></a></code></dt>
<dd><p>Settings for undo/redo history.</p></dd>
<dt><code><strong>onLoad</strong><span class="text-slate-400">?</span>: (editor: any) =&gt; void</code></dt>
<dd><p>Callback fired when the editor finishes loading and is ready for interaction.</p></dd>
<dt><code><strong>autofocus</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#focusposition"><span class="font-mono">FocusPosition</span></a></code></dt>
<dd><p>Controls initial cursor/focus position when the editor mounts.</p></dd>
<dt><code><strong>container</strong><span class="text-slate-400">?</span>: HTMLElement | (() =&gt; HTMLElement | null)</code></dt>
<dd><p>HTML element or factory function that returns the container element for rendering editor popovers and modals.</p></dd>
<dt><code><strong>initialContent</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#ricosdocument"><span class="font-mono">RicosDocument</span></a></code></dt>
<dd><p>Initial content for the editor. Only used during initialization — not a controlled value.</p></dd>
<dt><code><strong>keyboardShortcuts</strong><span class="text-slate-400">?</span>: any[]</code></dt>
<dd><p>Custom keyboard shortcuts to register with the editor.</p></dd>
<dt><code><strong>loadingPlaceholder</strong><span class="text-slate-400">?</span>: React.ReactNode</code></dt>
<dd><p>The placeholder to be displayed while the editor is loading.</p></dd>
<dt><code><strong>tabFocusBehavior</strong><span class="text-slate-400">?</span>: 'edit' | 'container'</code></dt>
<dd><p>Controls focus behavior when tabbing into the editor: - `'edit'`: (default) Immediately enables typing when editor receives keyboard focus. - `'container'`: Shows focus ring but requires Enter/key press to start editing.</p>
<p>Default: <code>'edit'</code></p></dd>
<dt><code><strong>colorManager</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#colorpalettemanager"><span class="font-mono">ColorPaletteManager</span></a></code></dt>
<dd><p>Unified color manager for sharing custom colors across all plugins.</p></dd>
<dt><code><strong>modalSettings</strong><span class="text-slate-400">?</span>: ModalSettings</code></dt>
<dd></dd>
<dt><code><strong>pickMedia</strong><span class="text-slate-400">?</span>: _PublicPickMedia</code></dt>
<dd><p>Pick media implementation used by media-related plugins (e.g., image, gallery).</p></dd>
<dt><code><strong>visitorUploads</strong><span class="text-slate-400">?</span>: _VisitorUploads</code></dt>
<dd><p>Visitor upload settings used by file-upload-capable plugins.</p></dd>
<dt><code><strong>fileUploadSizeLimit</strong><span class="text-slate-400">?</span>: number</code></dt>
<dd><p>Maximum file size limit (in bytes) for uploads.</p></dd>
<dt><code><strong>htmlAttributes</strong><span class="text-slate-400">?</span>: {         autoCapitalize?: string;         spellCheck?: boolean;         autoComplete?: string;         autoCorrect?: string;         tabIndex?: number;         ariaLabel?: string;     }</code></dt>
<dd><p>HTML attributes applied to the editor's content-editable element.</p></dd>
<dt><code><strong>isMobile</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, renders in mobile-optimized mode.</p></dd>
<dt><code><strong>generalLinkSettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#generallinksettings"><span class="font-mono">GeneralLinkSettings</span></a></code></dt>
<dd><p>Link settings applied globally to all linkable plugins.</p></dd>
<dt><code><strong>locale</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Two-letter locale code (e.g., `'en'`, `'he'`). Determines the language and text direction of modals and settings.</p></dd>
<dt><code><strong>localeContent</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Locale for content, when different from UI locale. If not defined, `locale` is used.</p></dd>
<dt><code><strong>onError</strong><span class="text-slate-400">?</span>: (error: string) =&gt; void</code></dt>
<dd><p>Callback fired when an error occurs in a Ricos component.</p></dd>
<dt><code><strong>theme</strong><span class="text-slate-400">?</span>: <a href="/api-docs/theming#ricostheme"><span class="font-mono">RicosTheme</span></a></code></dt>
<dd><p>Theme configuration for colors, fonts, and custom styles.</p></dd>
<dt><code><strong>textAlignment</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#textalignment"><span class="font-mono">TextAlignment</span></a></code></dt>
<dd><p>Default text alignment direction.</p></dd>
<dt><code><strong>onAtomicBlockFocus</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#onatomicblockfocus"><span class="font-mono">onAtomicBlockFocus</span></a></code></dt>
<dd><p>Callback fired when an atomic block (plugin) receives focus.</p></dd>
<dt><code><strong>iframeSandboxDomain</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Domain used to render UGC source code in an isolated sandboxed iframe.</p></dd>
<dt><code><strong>textWrap</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, wraps text around left/right-aligned plugins.</p></dd>
<dt><code><strong>debugMode</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#debugmode"><span class="font-mono">DebugMode</span></a>[]</code></dt>
<dd><p>Debug mode flags for enabling development-time diagnostics.</p></dd>
<dt><code><strong>environment</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#environment"><span class="font-mono">Environment</span></a></code></dt>
<dd><p>Theme variant to apply: - `'businessManager'`: BM theme is applied and cannot be overridden. - `'liveSite'`: liveSite theme is applied and can be overridden via `theme`.   'liveSite'</p></dd>
<dt><code><strong>baseUrl</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Base URL of the site, used to resolve page-relative URLs. Used by links, buttons, gallery items, and vertical embeds to construct full hrefs from relative paths (e.g., `/my-page` → `https://example.com/my-site/my-page`).  If not provided, fetched automatically from `/_api/ricos-site-pages/base-url`.</p></dd>
<dt><code><strong>biSettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#bisettings"><span class="font-mono">BISettings</span></a></code></dt>
<dd><p>Business intelligence configuration for analytics and tracking.</p></dd>
<dt><code><strong>wixClient</strong><span class="text-slate-400">?</span>: WixClient</code></dt>
<dd><p>Authenticated `WixClient` from `@wix/sdk`. When provided, takes priority over `instance` for authorizing every request the editor makes (media uploads, link previews, etc.). Use this in headless apps where you own the React tree.</p></dd>
<dt><code><strong>instance</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Per-session Wix instance token. Used when no `wixClient` is provided — Ricos builds an internal client from it. Typically set by Wix-served host pages.</p></dd>
<dt><code><strong>msid</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Metasite ID. Scopes storage and CDN traffic to the owning site when no `wixClient` is given.</p></dd>
<dt><code><strong>shouldNormalizeRichContent</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, normalises rich content via `normalizeRicosDocument` before rendering. Set to `false` to skip normalisation when content is already known to be valid.   true</p></dd>
<dt><code><strong>mediaSettings</strong><span class="text-slate-400">?</span>: MediaSettings</code></dt>
<dd><p>Media-upload, capture, and resolution settings shared across media plugins.</p></dd>
<dt style="opacity: 0.5;"><code><strong>linkSettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#linksettings"><span class="font-mono">LinkSettings</span></a></code></dt>
<dd><p><em>Deprecated.</em> Use `generalLinkSettings` instead.</p></dd>
<dt style="opacity: 0.5;"><code><strong>experiments</strong><span class="text-slate-400">?</span>: AvailableExperiments</code></dt>
<dd><p><em>Deprecated.</em> Not used at runtime.</p></dd>
</dl>

### Ricos Editor Imperative API

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosEditorAPI</span>

Imperative API for controlling a mounted `RicosEditor`. Accessed via a React ref — `const editorRef = useRef<RicosEditorType>(null)` — and read inside event handlers (publish flows, programmatic content swaps, focus/undo/redo controls). For UI that needs to react to editor state in render — toolbar enable/disable, undo-stack indicators — prefer the reactive hooks (`useToolbarProps`, `useUndoState`) over polling these methods.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosEditorAPI</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>focus</strong>: (focusPosition?: FocusPosition): void;</code></dt>
<dd><p>Focuses the editor. Optionally moves the cursor to `focusPosition`.</p></dd>
<dt><code><strong>blur</strong>: (): void;</code></dt>
<dd><p>Removes focus from the editor.</p></dd>
<dt><code><strong>undo</strong>: (): void;</code></dt>
<dd><p>Undoes the last editor action.</p></dd>
<dt><code><strong>redo</strong>: (): void;</code></dt>
<dd><p>Redoes the last undone action.</p></dd>
<dt><code><strong>getContent</strong>: (postId?: string, forPublish?: boolean, shouldRemoveErrorBlocks?: boolean): Promise&lt;RicosDocument&gt;;</code></dt>
<dd><p>Returns the current content as a `RicosDocument`. Resolves after any pending async operations.</p></dd>
<dt><code><strong>getContentPromise</strong>: (props: {         flush?: boolean;         publishId?: string;     } | undefined): Promise&lt;RicosDocument&gt;;</code></dt>
<dd><p>Returns a promise of the current content. `publishId` — used for BI (`onPublish` event), set it when called before publishing. `flush` — triggers editor blur to force pending changes (e.g., Android composition mode) before reading content.</p></dd>
<dt><code><strong>getContentTraits</strong>: (): {         isEmpty: boolean;         isContentChanged: boolean;         isLastChangeEdit: boolean;     };</code></dt>
<dd><p>Returns content traits: whether the editor is empty, content has changed, and the last change was an edit.</p></dd>
<dt><code><strong>setContent</strong>: (content: RicosDocument): void;</code></dt>
<dd><p>Replaces the editor content. Imperative replacement for the removed `injectedContent` prop.</p></dd>
<dt><code><strong>getEditorContainer</strong>: (): HTMLElement | undefined;</code></dt>
<dd><p>Returns the DOM element wrapping the editor's content-editable, if mounted.</p></dd>
<dt><code><strong>openModal</strong>: (options: {         type: 'AI_IMAGE' | 'INLINE_AI' | 'SHORTCUTS_SHEET' | 'AI_WIDGET';     }) =&gt; void</code></dt>
<dd><p>Opens a built-in modal (AI image generation, inline AI, keyboard shortcuts sheet, or AI widget).</p></dd>
<dt><code><strong>useToolbarProps</strong>: (type: UseToolbarPropsType): ToolbarPropsResult;</code></dt>
<dd><p>Subscribe to the reactive button list for a toolbar. Re-runs on selection and content changes, so consumers rendering a custom toolbar stay in sync automatically. Must be called inside a React component.</p></dd>
<dt><code><strong>useInlineAiState</strong>: (invocationId: string) =&gt; unknown</code></dt>
<dd></dd>
<dt><code><strong>prePublish</strong>: (): Promise&lt;RicosDocument&gt;;</code></dt>
<dd><p>Runs plugin pre-publish hooks (e.g. poll sync) and returns the content.</p></dd>
<dt><code><strong>useUndoState</strong>: () =&gt; {         isUndoStackEmpty: boolean;         isRedoStackEmpty: boolean;     }</code></dt>
<dd><p>Reactive undo/redo state. Must be called inside a React component.</p></dd>
</dl>

```tsx widget
import type { RicosDocument, RicosEditorType } from '@wix/ricos';
import { pluginImage, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  const editorRef = React.useRef<RicosEditorType>(null);

  // @essential-start
  const UndoButtons = () => {
    const { isUndoStackEmpty, isRedoStackEmpty } = editorRef.current?.useUndoState() ?? {
      isUndoStackEmpty: true,
      isRedoStackEmpty: true,
    };
    return (
      <>
        <button onClick={() => editorRef.current?.undo()} disabled={isUndoStackEmpty} type="button">
          Undo
        </button>
        <button onClick={() => editorRef.current?.redo()} disabled={isRedoStackEmpty} type="button">
          Redo
        </button>
      </>
    );
  };

  const handlePublish = async () => {
    const document = await editorRef.current?.prePublish();
    alert(`Publishing ${document?.nodes.length ?? 0} nodes`);
  };

  const handleReset = () => editorRef.current?.setContent(initialContent);

  return (
    <>
      <RicosEditor
        ref={editorRef}
        initialContent={initialContent}
        plugins={[pluginImage()]}
        placeholder="Edit me — drag an image in or use the toolbar"
      />
      <UndoButtons />
      <button onClick={handlePublish} type="button">
        Publish
      </button>
      <button onClick={handleReset} type="button">
        Reset
      </button>
    </>
  );
  // @essential-end
};

const initialContent: RicosDocument = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'p1',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          nodes: [],
          textData: { text: 'Edit, undo, publish, reset.', decorations: [] },
        },
      ],
    },
    {
      type: 'IMAGE',
      id: 'img1',
      nodes: [],
      imageData: {
        containerData: {
          width: { size: 'CONTENT' },
          alignment: 'CENTER',
          textWrap: true,
        },
        image: {
          src: { url: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_1280.jpg' },
          width: 5120,
          height: 3413,
        },
        altText: 'A tree during sunset.',
      },
    },
  ],
};
```


## Toolbars

Ricos ships with four consumer-facing toolbars, all configured through a single `toolbars` prop on `RicosEditor`:

- **Text toolbar** — formatting controls shown when the user selects text (floating by default, or rendered into a container)
- **Insert toolbar** — the inline plus button on empty lines and the footer plugin bar
- **Plugin toolbar** — the bubble that appears when a plugin node or decoration (e.g. a link) is selected
- **Quick actions** — the slash-command menu triggered by typing `/`

Each is optional — omit a section to keep its default behaviour. Fields are grouped by concept, not by the low-level rendering variant, so the same config drives desktop and mobile without platform-specific branches.

##### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">ToolbarsConfig</span>

Configuration for the editor's consumer-facing toolbars — text formatting, insert plugin menus, plugin context bubbles and quick actions.  Fields are grouped by concept (`text`, `insert`, `plugin`, `quickActions`) so the same config drives desktop and mobile without platform-specific branching.

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> ToolbarsConfig = {     text?: TextToolbarConfig;     insert?: InsertToolbarConfig;     plugin?: PluginToolbarConfig;     quickActions?: <a href="#quickactionsconfig"><span class="font-mono">QuickActionsConfig</span></a>; }</code></pre>

### Text toolbar

Two mutually exclusive modes: `floating` (above the selection) and `static` (rendered into a DOM container the consumer owns).

#### Floating mode with custom buttons

Filter the default button list and register your own components:

```tsx widget
import { FORMATTING_BUTTONS, RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Floating text toolbar with:
 * - `buttons` callback filtering out CODE_BLOCK from defaults and appending
 *   a custom button name
 * - `customButtons` registering a React component for that name
 *
 * The callback receives `defaultButtons` already resolved to the platform's
 * default text button ids.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        text: {
          mode: 'floating',
          buttons: ({ defaultButtons }) => [
            ...defaultButtons.filter(id => id !== FORMATTING_BUTTONS.CODE_BLOCK),
            'MY_INFO',
          ],
          customButtons: {
            MY_INFO: {
              component: () => (
                // oxlint-disable-next-line
                <div onClick={() => console.log('info!')} style={{ padding: '0 8px' }}>
                  ℹ︎
                </div>
              ),
            },
          },
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'בחר טקסט כדי לראות את סרגל הכלים הצף!'
          : 'Select text to see the floating toolbar!'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

The `buttons` callback receives `defaultButtons` — the platform-resolved list of built-in button ids. Return the desired order; unknown ids are dropped silently. Use `customButtons` to register React components keyed by the ids you reference.

#### Static mode

Render the text toolbar into a container outside the editor — useful when the page already has a fixed toolbar area:

```tsx widget
import { RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Static text toolbar rendered into a container the consumer owns.
 *
 *   toolbars: { text: { mode: 'static', container, alignment: 'left' } }
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  const [toolbarContainer, setToolbarContainer] = React.useState<HTMLDivElement | null>(null);

  return (
    <div style={{ display: 'grid', gap: 16 }}>
      <div
        ref={setToolbarContainer}
        style={{ minHeight: 48, border: '1px dashed #ccc', padding: 8 }}
      />
      {/* @essential-start */}
      <RicosEditor
        initialContent={content}
        plugins={editorPlugins}
        toolbars={
          toolbarContainer
            ? { text: { mode: 'static', container: toolbarContainer, alignment: 'left' } }
            : undefined
        }
        onChange={setContent}
        placeholder={locale === 'he' ? 'הוסף קצת טקסט!' : 'Add some text!'}
        isMobile={isMobile}
        environment={environment}
        locale={locale}
        localeContent={localeContent}
      />
      {/* @essential-end */}
    </div>
  );
};
```

Set `container` to any `HTMLElement`. `alignment` controls horizontal positioning inside that container.

#### Mobile

The same config drives both platforms — the runtime picks the mobile variant automatically when `isMobile` is on. `defaultButtons` in the callback is already resolved to the platform's defaults (headings inline, no font family / AI controls, `AddPlugin` inserted), so one filter works everywhere:

```tsx widget
import { FORMATTING_BUTTONS, RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Text formatting toolbar on mobile.
 *
 * On mobile the runtime automatically renders the mobile variant of whichever
 * `text.mode` you picked. The same config drives both platforms:
 *
 * - Headings render as individual buttons on mobile (no dropdown).
 * - Font family / AI / indent controls are not part of the mobile defaults.
 * - `AddPlugin` appears inline at the start of the mobile toolbar.
 *
 * The `buttons` callback is invoked once per platform with the
 * platform-resolved `defaultButtons`, so the same filter/reorder logic
 * applies to both without extra code.
 *
 * Toggle the **isMobile** switch in the dev panel to see the desktop variant
 * — this example forces mobile on mount.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
    environment: 'businessManager',
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      // Force mobile so the mobile text toolbar is visible without toggling.
      isMobile
      toolbars={{
        text: {
          mode: 'floating',
          // Same callback on desktop and mobile — `defaultButtons` is
          // platform-resolved. Drop the CODE_BLOCK button on both platforms.
          buttons: ({ defaultButtons }) =>
            defaultButtons.filter(id => id !== FORMATTING_BUTTONS.CODE_BLOCK),
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'בחירת טקסט — סרגל הכלים לעיצוב לנייד מופיע.'
          : 'Select text — the mobile formatting toolbar appears.'
      }
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

### Insert toolbar

Two surfaces share a single `buttons` list: the inline plus button's popup menu and the footer bar.

#### Footer with pinned plugins

Pin a subset of installed plugins to the footer. The rest stay accessible via the "More" menu:

```tsx widget
import { INSERT_PLUGIN_BUTTONS, RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Footer insert toolbar pinning only a subset of installed plugins.
 *
 * All plugins remain installed; only the listed ones get a dedicated footer
 * button. The rest live under the "More" menu.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        insert: {
          footer: {
            pluginsToDisplayInToolbar: [
              INSERT_PLUGIN_BUTTONS.IMAGE,
              INSERT_PLUGIN_BUTTONS.VIDEO,
              INSERT_PLUGIN_BUTTONS.GIF,
            ],
          },
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'רק תמונה, סרטון וגיף מוצמדים למטה!'
          : 'Only image, video & gif are pinned below!'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

Use the `INSERT_PLUGIN_BUTTONS` constants to reference built-in plugin buttons by id.

#### Custom plus button trigger

Override what happens when the user clicks the inline plus button. `defaultTrigger` opens the built-in menu — call it to preserve the default, skip it to replace entirely:

```tsx widget
import { RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Custom behaviour for the inline plus button.
 *
 * `insert.plusButton.onTrigger` receives `defaultTrigger` — call it to keep
 * the built-in open-menu behaviour, skip it to replace entirely, or wrap
 * with extra logic (tracking, permission gates, a custom panel, …).
 *
 * This example shows a "track + fall through" pattern. Click the plus button
 * on an empty line: you'll see a log line and then the default menu opens.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        insert: {
          plusButton: {
            onTrigger: defaultTrigger => {
              // oxlint-disable-next-line
              console.log('[plusButton] clicked — tracking, then opening menu');
              defaultTrigger?.();
            },
          },
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'לחץ על כפתור הפלוס שליד שורה ריקה!'
          : 'Click the plus button next to an empty line!'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

### Plugin toolbar

The `buttons` callback is invoked every time a plugin node or decoration is focused, with a discriminated context: `{ type: 'node', nodeType }` or `{ type: 'decoration', decoration }`.

#### Filter buttons on a specific plugin

Drop or reorder buttons only for the plugin you care about, pass through everything else:

```tsx widget
import { RicosEditor, RicosPluginToolbarNames } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Filter and reorder buttons on a specific plugin node's toolbar.
 *
 * `plugin.buttons` receives a discriminated ctx. When `type === 'node'`, the
 * `nodeType` tells you which plugin's toolbar is about to render. Return an
 * array of ids in the desired order — unknown ids are silently dropped.
 *
 * This example drops the "size" button from the IMAGE plugin toolbar and
 * moves "alignment" to the front, leaving other plugin toolbars untouched.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.imageContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        plugin: {
          buttons: ctx => {
            if (ctx.type === 'node' && ctx.nodeType === RicosPluginToolbarNames.IMAGE) {
              const alignmentId = ctx.defaultButtons.find(id =>
                id.toLowerCase().includes('alignment')
              );
              const rest = ctx.defaultButtons.filter(
                id => id !== alignmentId && !id.toLowerCase().includes('size')
              );
              return alignmentId ? [alignmentId, ...rest] : rest;
            }
            return ctx.defaultButtons;
          },
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'לחץ על התמונה - סרגל הכלים שלה מותאם אישית.'
          : 'Click the image — its toolbar is customized.'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

The `RicosPluginToolbarNames` enum provides typed names for every built-in plugin / decoration.

#### Hide a decoration bubble entirely

Return `[]` from the callback to suppress the toolbar for that selection — this is how you hide the LINK bubble:

```tsx widget
import { RicosEditor, RicosPluginToolbarNames } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Hide the LINK bubble toolbar via `plugin.buttons`.
 *
 * Links are surfaced as a *decoration* plugin toolbar. Returning `[]` for the
 * matching ctx hides the bubble entirely.
 *
 * Click inside a link in the content — the usual bubble no longer appears.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        plugin: {
          buttons: ctx =>
            ctx.type === 'decoration' && ctx.decoration === RicosPluginToolbarNames.LINK
              ? []
              : ctx.defaultButtons,
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'הקלד טקסט, הוסף קישור ולאחר מכן לחץ עליו - ללא בועה.'
          : 'Type text, add a link, then click it — no bubble.'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

### Quick actions

The slash-command menu. Pass `placeholder` to override the prompt shown in the slash input (default: `'Type /'`), or `disabled: true` to remove the menu entirely:

```tsx widget
import { RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Custom quick-actions placeholder.
 *
 * Type `/` in an empty line to open the slash menu with the custom placeholder.
 * Set `disabled: true` to turn off the slash menu entirely.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        quickActions: {
          placeholder: 'Type / to add a block',
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'לחץ על / בשורה ריקה כדי לפתוח את תפריט הפעולות המהירות'
          : 'Press / on an empty line to open the quick-actions menu'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

### Disabling everything

Set `disabled: true` on each section to remove all consumer-facing chrome — useful when rendering fully external toolbars:

```tsx widget
import { RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Minimal editor — every consumer-facing toolbar turned off.
 *
 * Shows how `*.disabled: true` combines:
 *   - `text.disabled: true`              — no floating/static/mobile formatting bar
 *   - `insert.plusButton.disabled: true` — no inline plus button
 *   - `insert.footer.disabled: true`     — no footer plugin bar
 *   - `plugin.disabled: true`            — no plugin bubble toolbars (also hides LINK)
 *   - `quickActions.disabled: true`      — no slash menu
 *
 * The result is a bare content-editable with no chrome — useful when the
 * consumer renders their own toolbars externally.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        text: { mode: 'floating', disabled: true },
        insert: {
          plusButton: { disabled: true },
          footer: { disabled: true },
        },
        plugin: { disabled: true },
        quickActions: { disabled: true },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he' ? 'עורך פשוט - ללא סרגלי כלים כלל.' : 'A bare editor — no toolbars at all.'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

### External toolbars via `useToolbarProps`

When the default placements don't fit your design, hide the internal toolbars and render buttons wherever you want — `RicosEditorAPI.useToolbarProps(type)` returns the current button state and re-runs on selection and content changes:

```tsx widget
import type { RicosEditorAPI } from '@wix/ricos';
import { RicosEditor, ToolbarType } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  ExternalToolbar,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * External formatting toolbar — render your own toolbar DOM off to the side.
 *
 * The pattern is:
 *   1. Hide the editor's internal toolbars via `toolbars`.
 *   2. Read reactive button state via `editorApi.useToolbarProps(type)`.
 *   3. Render the buttons wherever the consumer wants.
 *
 * The hook re-runs on selection and content changes, so the external toolbar
 * stays in sync automatically.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  const [editorApi, setEditorApi] = React.useState<RicosEditorAPI | null>(null);

  return (
    <>
      {editorApi && <ConsumerToolbar editorApi={editorApi} />}
      {/* @essential-start */}
      <RicosEditor
        initialContent={content}
        plugins={editorPlugins}
        toolbars={{
          // Hide everything the editor would render itself…
          text: { mode: 'floating', disabled: true },
          insert: { plusButton: { disabled: true }, footer: { disabled: true } },
        }}
        onChange={setContent}
        placeholder={'Select text — the buttons above will activate.'}
        ref={setEditorApi}
        isMobile={isMobile}
        environment={environment}
        locale={locale}
        localeContent={localeContent}
      />
    </>
  );
};

const ConsumerToolbar: React.FC<{ editorApi: RicosEditorAPI }> = ({ editorApi }) => {
  // …then read button state from the hook and render it ourselves.
  const formatting = editorApi.useToolbarProps(ToolbarType.FORMATTING);
  const plugin = editorApi.useToolbarProps(ToolbarType.PLUGIN);

  const buttons =
    'buttons' in plugin && Object.keys(plugin.buttons).length ? plugin.buttons : formatting.buttons;

  return <ExternalToolbar buttons={buttons} />;
      // @essential-end
};
```

Pass a `ToolbarType` (e.g. `ToolbarType.FORMATTING`, `ToolbarType.PLUGIN`) to pick which button set to read. Each returned button exposes `onClick`, `isActive`, `isDisabled`, `getIcon`, `getLabel`, and more — see `ToolbarButtonProps` for the full shape.

### Ricos Viewer Props

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosViewerProps</span>

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosViewerProps</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>children</strong><span class="text-slate-400">?</span>: React.ReactElement</code></dt>
<dd><p>Optional child element to render inside the viewer.</p></dd>
<dt><code><strong>plugins</strong><span class="text-slate-400">?</span>: (ViewerPlugin | any)[]</code></dt>
<dd><p>Array of viewer plugins to enable. Each plugin adds rendering capabilities for specific content types.</p></dd>
<dt><code><strong>seoSettings</strong><span class="text-slate-400">?</span>: boolean | any</code></dt>
<dd><p>SEO settings for server-side rendering. When `true`, enables default SEO mode.</p></dd>
<dt><code><strong>textSelectionToolbar</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, shows a floating toolbar when the user selects text in the viewer.</p></dd>
<dt><code><strong>content</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#ricosdocument"><span class="font-mono">RicosDocument</span></a></code></dt>
<dd><p>The rich content to display. Required for the viewer to render anything.</p></dd>
<dt><code><strong>addAnchors</strong><span class="text-slate-400">?</span>: boolean | string</code></dt>
<dd><p>When `true`, adds anchor elements to content blocks for deep linking. Pass a string to use as a prefix for generated anchor IDs.</p></dd>
<dt><code><strong>onNodeClick</strong><span class="text-slate-400">?</span>: ({ node }: {         node: any;     }) =&gt; void</code></dt>
<dd><p>Callback fired when a user clicks on a content node in the viewer.</p></dd>
<dt><code><strong>jsdomMode</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, renders in jsdom-compatible mode for server-side rendering environments.</p></dd>
<dt><code><strong>isPrint</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, renders in print-optimized mode (e.g., for `window.print()`).</p></dd>
<dt><code><strong>isMobile</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, renders in mobile-optimized mode.</p></dd>
<dt><code><strong>generalLinkSettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#generallinksettings"><span class="font-mono">GeneralLinkSettings</span></a></code></dt>
<dd><p>Link settings applied globally to all linkable plugins.</p></dd>
<dt><code><strong>locale</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Two-letter locale code (e.g., `'en'`, `'he'`). Determines the language and text direction of modals and settings.</p></dd>
<dt><code><strong>localeContent</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Locale for content, when different from UI locale. If not defined, `locale` is used.</p></dd>
<dt><code><strong>onError</strong><span class="text-slate-400">?</span>: (error: string) =&gt; void</code></dt>
<dd><p>Callback fired when an error occurs in a Ricos component.</p></dd>
<dt><code><strong>theme</strong><span class="text-slate-400">?</span>: <a href="/api-docs/theming#ricostheme"><span class="font-mono">RicosTheme</span></a></code></dt>
<dd><p>Theme configuration for colors, fonts, and custom styles.</p></dd>
<dt><code><strong>textAlignment</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#textalignment"><span class="font-mono">TextAlignment</span></a></code></dt>
<dd><p>Default text alignment direction.</p></dd>
<dt><code><strong>onAtomicBlockFocus</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#onatomicblockfocus"><span class="font-mono">onAtomicBlockFocus</span></a></code></dt>
<dd><p>Callback fired when an atomic block (plugin) receives focus.</p></dd>
<dt><code><strong>iframeSandboxDomain</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Domain used to render UGC source code in an isolated sandboxed iframe.</p></dd>
<dt><code><strong>textWrap</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, wraps text around left/right-aligned plugins.</p></dd>
<dt><code><strong>debugMode</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#debugmode"><span class="font-mono">DebugMode</span></a>[]</code></dt>
<dd><p>Debug mode flags for enabling development-time diagnostics.</p></dd>
<dt><code><strong>environment</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#environment"><span class="font-mono">Environment</span></a></code></dt>
<dd><p>Theme variant to apply: - `'businessManager'`: BM theme is applied and cannot be overridden. - `'liveSite'`: liveSite theme is applied and can be overridden via `theme`.   'liveSite'</p></dd>
<dt><code><strong>baseUrl</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Base URL of the site, used to resolve page-relative URLs. Must be provided explicitly. If missing, page links will render as relative paths without the site domain.</p></dd>
<dt><code><strong>biSettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#bisettings"><span class="font-mono">BISettings</span></a></code></dt>
<dd><p>Business intelligence configuration for analytics and tracking.</p></dd>
<dt><code><strong>wixClient</strong><span class="text-slate-400">?</span>: WixClient</code></dt>
<dd><p>Authenticated `WixClient` from `@wix/sdk`. When provided, takes priority over `instance` for authorizing every request the viewer makes. Use this in headless apps where you own the React tree.</p></dd>
<dt><code><strong>instance</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Per-session Wix instance token. Used when no `wixClient` is provided — Ricos builds an internal client from it.</p></dd>
<dt><code><strong>msid</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Metasite ID. Scopes storage and CDN traffic to the owning site when no `wixClient` is given.</p></dd>
<dt><code><strong>shouldNormalizeRichContent</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, normalises rich content via `normalizeRicosDocument` before rendering. Set to `false` to skip normalisation when content is already known to be valid.   true</p></dd>
<dt><code><strong>mediaSettings</strong><span class="text-slate-400">?</span>: MediaSettings</code></dt>
<dd><p>Media-upload, capture, and resolution settings shared across media plugins.</p></dd>
<dt style="opacity: 0.5;"><code><strong>linkSettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#linksettings"><span class="font-mono">LinkSettings</span></a></code></dt>
<dd><p><em>Deprecated.</em> Use `generalLinkSettings` instead.</p></dd>
<dt style="opacity: 0.5;"><code><strong>experiments</strong><span class="text-slate-400">?</span>: AvailableExperiments</code></dt>
<dd><p><em>Deprecated.</em> Not used at runtime.</p></dd>
</dl>

#### `seoSettings`

```jsx
seoSettings?: boolean | {
  paywall: {
    className: string;
    index: number;
  };
}
```

When given a truthy value, renders RicosViewer in a SEO optimized mode.
An object allows extended configuration.

##### `paywall`

className - Content blocked by a payall will be marked up using this string (This class will be used by Googlebot to discern between paid and free content on a page level)
index - number of free content blocks

#### `addAnchors`

TODO

#### `textSelectionToolbar`

When given a truthy value, text-selection-toolbar appears inline when selecting some text and allows tweet text.

Select some text below to see text-selection-toolbar

```tsx widget
import '@wix/ricos/css/all-plugins-viewer.css';
import { quickStartViewerPlugins, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <div style={{ position: 'relative' }}>
      <RicosViewer textSelectionToolbar plugins={quickStartViewerPlugins()} content={demoContent} />{' '}
    </div>
    // @essential-end
  );
};
```

## Theming

### Ricos Theme

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosTheme</span>

Theme input for `RicosEditor` and `RicosViewer`. Values flow into CSS variables consumed by every plugin, the toolbars, and the settings panels — prefer this over wrapping Ricos with external CSS, because external CSS only reaches the editable area, not the chrome. Pair `colorPalette` (broad colors) with `customStyles` (per-element overrides) for full control.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosTheme</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>colorPalette</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#ricospalette"><span class="font-mono">RicosPalette</span></a></code></dt>
<dd><p>Global color tokens shared across all plugins, toolbars, and settings panels.</p></dd>
</dl>

<div class="pl-4 border-l-2 border-slate-200 mb-4">

```tsx widget
import { fromPlainText, pluginHashtag, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return (
    <div style={{ background: '#C9C9C9' }}>
      <RicosEditor
        initialContent={fromPlainText('This is a #hashtag, see it has a unique color.')}
        placeholder="Type here!"
        theme={{
          colorPalette: {
            format: 'color',
            bgColor: '#0E092B',
            textColor: '#FFFFFF',
            accent1: '#D6FF00',
          },
        }}
        plugins={[pluginHashtag()]}
      />
    </div>
  );
  // @essential-end
};
```

</div>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>typography</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#ricostypography"><span class="font-mono">RicosTypography</span></a></code></dt>
<dd><p>Typography tokens. `fontFamily` cascades to every text node unless a `customStyles.&lt;element&gt;.fontFamily` overrides it.</p></dd>
<dt><code><strong>customStyles</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#ricoscustomstyles"><span class="font-mono">RicosCustomStyles</span></a></code></dt>
<dd><p>Per-element style overrides for headings, paragraphs, quotes, plugin cards, and the editor container itself.</p></dd>
</dl>

<div class="pl-4 border-l-2 border-slate-200 mb-4">

```tsx widget
import { fromPlainText, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  const theme = {
    customStyles: {
      h2: {
        fontFamily: 'Times',
        fontSize: '26px',
        color: 'orange',
        fontStyle: 'italic',
        textDecoration: 'underline',
      },
      h3: {
        fontFamily: 'Tahoma',
        fontSize: '20px',
        color: 'purple',
        textDecoration: 'none',
      },
      quote: {
        lineHeight: '24px',
        fontWeight: 'bold',
        color: 'brown',
      },
    },
  };

  return (
    <RicosEditor
      initialContent={fromPlainText('Apply heading 2 or 3 from the toolbar to see the styles.')}
      placeholder="Type here!"
      theme={theme}
    />
  );
  // @essential-end
};
```

</div>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>settingsStyles</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#ricossettingsstyles"><span class="font-mono">RicosSettingsStyles</span></a></code></dt>
<dd><p>Style overrides for settings modals (z-index layering). Most fields are deprecated — only `modals` is supported going forward.</p></dd>
<dt><code><strong>nativeStyle</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#ricosnativestyles"><span class="font-mono">RicosNativeStyles</span></a></code></dt>
<dd><p>React-Native-flavoured style overrides; only consumed when `useNativeStyles` is `true`.</p></dd>
<dt><code><strong>useWixStyles</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, applies the Wix Studio default theme on top of the supplied values.</p></dd>
<dt><code><strong>useNativeStyles</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, switches the renderer to consume `nativeStyle` instead of CSS overrides — used by mobile builds.</p></dd>
<dt><code><strong>breakout</strong><span class="text-slate-400">?</span>: {         rule: ({ node }: {             node: <a href="/api-docs/related-types#ricosdocumentnode"><span class="font-mono">RicosDocumentNode</span></a>;         }) =&gt; 'fullWidth' | 'normal';         normalPadding: {             start: CSSProperties['paddingInlineStart'];             end: CSSProperties['paddingInlineEnd'];         };         fullWidthPadding?: {             start: CSSProperties['paddingInlineStart'];             end: CSSProperties['paddingInlineEnd'];         };     }</code></dt>
<dd><p>Breakout layout: lets specific nodes flow beyond the content column to viewport edges. `rule` is called once per node at layout time; return `'fullWidth'` for the chosen nodes (typically images and galleries) and `'normal'` for everything else. `fullWidthPadding` overrides `normalPadding` for the `'fullWidth'` case.</p></dd>
</dl>

### Recipes

#### Choosing between `colorPalette` and `customStyles`

`colorPalette` is the broad brush — a small set of color tokens (`bgColor`, `textColor`, `accent1..4`, `shade1..3`) that flow into every plugin's CSS variables. Set it once and the editor, toolbars, settings panels, and every plugin card pick up the new colors together.

`customStyles` is the per-element scalpel. Reach for it when you need to override font, size, padding, or borders on a specific element (`h1`, `quote`, `editor`, `footerToolbar`, …). When both apply to the same element, `customStyles` wins.

#### Color formats

`RicosPalette.format` controls how Ricos interprets every other value in the palette:

- `'color'` — values are standard CSS color strings (hex, `rgb()`, named colors). This is what most consumers want.
- `'rgbTuple'` — values are bare `R, G, B` triples (e.g. `'132, 84, 252'`). Use this mode when you want Ricos to derive opacity variants downstream (`rgba(R, G, B, 0.5)` etc.) — applying an alpha channel to a hex string isn't possible.

The same key names work in both modes — only the format of the values changes.

```tsx widget
import { fromPlainText, pluginHashtag, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => (
  <div style={{ background: '#C9C9C9' }}>
    {/* @essential-start */}
    <RicosEditor
      initialContent={fromPlainText('This is a #hashtag, see it has a unique color.')}
      placeholder="Type here!"
      theme={{
        colorPalette: {
          format: 'color',
          bgColor: '#0E092B',
          textColor: '#FFFFFF',
          accent1: '#D6FF00',
        },
      }}
      plugins={[pluginHashtag()]}
    />
    {/* @essential-end */}
  </div>
);
```

#### Per-element `customStyles`

Most consumers only touch `h1..h6`, `p`, `link`, `quote`. The full surface is in the `RicosCustomStyles` table — link to it from the row above. A few less obvious entries worth knowing:

- `editor` — controls the editable container's `minHeight`/`maxHeight`, placeholder color, and the position of the inline plus button.
- `footerToolbar` — controls the inline plus-button bar at the bottom of the editor.
- `card` — the per-plugin selection chrome (selection border, background image, padding).
- `container` — the outermost wrapper's top/bottom padding.

```tsx widget
import { fromPlainText, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  const theme = {
    customStyles: {
      h2: {
        fontFamily: 'Times',
        fontSize: '26px',
        color: 'orange',
        fontStyle: 'italic',
        textDecoration: 'underline',
      },
      h3: {
        fontFamily: 'Tahoma',
        fontSize: '20px',
        color: 'purple',
        textDecoration: 'none',
      },
      quote: {
        lineHeight: '24px',
        fontWeight: 'bold',
        color: 'brown',
      },
    },
  };

  return (
    <RicosEditor
      initialContent={fromPlainText('Apply heading 2 or 3 from the toolbar to see the styles.')}
      placeholder="Type here!"
      theme={theme}
    />
  );
  // @essential-end
};
```

#### Breakout layout

`breakout.rule({ node }) => 'fullWidth' | 'normal'` runs once per node at layout time. Return `'fullWidth'` to let that node flow to the viewport edges; return `'normal'` (or omit the field entirely) to keep it inside the content column. `fullWidthPadding` overrides `normalPadding` for the `'fullWidth'` case, so you can leave a small inset even on edge-to-edge images.

Typical use: media plugins (`image`, `gallery`, `video`) go full-width on large viewports; text stays in the content column.

## Plugins

### Audio Plugin

The Audio plugin lets users upload audio files or embed audio from SoundCloud and Spotify.

#### Editor

```tsx widget
import { pluginAudio, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => (
  // @essential-start
  <RicosEditor placeholder={'Upload an audio file...'} plugins={[pluginAudio()]} />
  // @essential-end
);
```

##### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">AudioPluginEditorConfig</span>

Configuration for the Audio editor plugin. Pass to `pluginAudio()`.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> AudioPluginEditorConfig</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>exposeButtons</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#audiobutton"><span class="font-mono">AudioButton</span></a>[]</code></dt>
<dd><p>Controls which audio source buttons appear in the toolbar insert menu. By default, only the file-upload (`'audio'`) button is shown. Add `'soundCloud'` and `'spotify'` to enable embedding from those services.</p>
<p>Default: <code>`['audio']`</code></p></dd>
<dt><code><strong>disableDownload</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, disables the download button on newly inserted audio blocks.</p>
<p>Default: <code>true</code></p></dd>
<dt><code><strong>getAudioUrl</strong><span class="text-slate-400">?</span>: (src: {         id: string;     }) =&gt; string</code></dt>
<dd><p>Resolves an audio file ID to a full playback URL. Called when rendering audio nodes that store a relative source (e.g. Wix Media). If not provided, a default Wix Media resolver is used.</p></dd>
</dl>

To expose SoundCloud and Spotify embed buttons alongside the default file-upload button:

```tsx widget
import { pluginAudio, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => (
  // @essential-start
  <RicosEditor
    placeholder={'Upload audio, or embed from SoundCloud / Spotify...'}
    plugins={[pluginAudio({ exposeButtons: ['audio', 'soundCloud', 'spotify'] })]}
  />
  // @essential-end
);
```

#### Viewer

```tsx widget
// @essential-start
import '@wix/ricos/css/plugin-audio-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import { pluginAudioViewer, type RicosDocument, RicosViewer } from '@wix/ricos';
// @essential-end
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginAudioViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'foo',
      nodes: [],
      paragraphData: {
        textStyle: {
          textAlignment: 'AUTO',
        },
        indentation: 0,
      },
    },
    {
      type: 'AUDIO',
      id: '8q61c',
      nodes: [],
      audioData: {
        containerData: {
          alignment: 'CENTER',
          width: {
            size: 'CONTENT',
          },
          textWrap: true,
        },
        audio: {
          src: {
            id: 'mp3/f0f74f_48772df0375c41cd88e8e29370ccf899',
          },
        },
        disableDownload: true,
        coverImage: {
          src: {
            id: 'f0f74f_2973832f552e4002b58ec6abbe7fce71~mv2.png',
          },
          width: 436,
          height: 524,
        },
        name: 'Dear Fear',
        authorName: 'Kota The Friend',
      },
    },
  ],
} as RicosDocument;
```

##### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">AudioPluginViewerConfig</span>

Configuration for the Audio viewer plugin. Pass to `pluginAudioViewer()`.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> AudioPluginViewerConfig</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>getAudioUrl</strong><span class="text-slate-400">?</span>: (src: {         id: string;     }) =&gt; string</code></dt>
<dd><p>Resolves an audio file ID to a full playback URL. Called when rendering audio nodes that store a relative source (e.g. Wix Media). If not provided, a default Wix Media resolver is used.</p></dd>
</dl>

### Button Plugin

There are two types of buttons

1. Link Button - basically a link in a shape of a button.
2. Action Button - by passing an `onClick` prop callback, when clicking the button it triggers the
`onClick` action.

#### Usage

##### Editor

```tsx widget
import { pluginActionButton, pluginLinkButton, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosEditor
      placeholder={'Add link or action button'}
      plugins={[pluginLinkButton(), pluginActionButton()]}
    />
    // @essential-end
  );
};
```

##### Viewer

```tsx widget
import '@wix/ricos/css/plugin-button-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginActionButtonViewer, pluginLinkButtonViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosViewer
      content={content}
      plugins={[pluginLinkButtonViewer(), pluginActionButtonViewer()]}
    />
    // @essential-end
  );
};

const content = {
  nodes: [
    {
      type: 'BUTTON',
      id: 'puer621',
      buttonData: {
        containerData: {
          width: {
            size: 'ORIGINAL',
          },
          alignment: 'CENTER',
          spoiler: {},
          height: {},
          textWrap: true,
        },
        type: 'LINK',
        styles: {},
        text: 'Link button',
        link: {
          url: 'https://ricos.dev',
          target: 'BLANK',
          rel: {
            nofollow: true,
            sponsored: false,
          },
        },
      },
    },
    {
      type: 'PARAGRAPH',
      id: '0bkal481',
    },
    {
      type: 'BUTTON',
      id: 'e65qp515',
      buttonData: {
        containerData: {
          width: {
            size: 'ORIGINAL',
          },
          alignment: 'CENTER',
          spoiler: {},
          height: {},
          textWrap: true,
        },
        type: 'ACTION',
        styles: {},
        text: 'Action button',
      },
    },
    {
      type: 'PARAGRAPH',
      id: 'rqftp516',
    },
  ],
} as RichContent;
```

#### Link & Action Button Config API

##### `colors`

Control Button colors

```jsx
colors?: {
  color1?: string,
  color2?: string,
  color3?: string,
  ...
  `color${Number}?`: string,
}
```

Example value:

```jsx
colors = {
  color1: '#FEFDFD',
  color2: '#D5D4D4',
  color3: '#000000',
  color4: '#000000',
  color5: '#000000',
  color6: '#ABCAFF',
  color7: '#81B0FF',
  color8: '#0261FF',
  color9: '#0141AA',
  color10: '#012055',
};
```

##### `onTextColorAdded`

```jsx
onTextColorAdded: (color: string) => string[]
```

A function that receives a color and returns user text colors.

##### `onBackgroundColorAdded`

```jsx
onBackgroundColorAdded: (color: string) => string[]
```

A function that receives a color and returns user background colors.

##### `onBorderColorAdded`

```jsx
onBorderColorAdded: (color: string) => string[]
```

A function that receives a color and returns user border colors.

##### `getTextColors`

```jsx
getTextColors: () => string[]
```

A function that returns user text colors.

##### `getBorderColors`

```jsx
getBorderColors: () => string[]
```

A function that returns user border colors.

##### `getBackgroundColors`

```jsx
getBackgroundColors: () => string[]
```

A function that returns user background colors.

#### Action Button Config API

##### `onClick`

```jsx
onClick: () => void
```

A function that is triggered when clicking the button, it will trigger the `onClick` action.

### Code Block Plugin

The Code Block plugin lets users insert formatted code snippets.

> **Note:** There is no `pluginCodeBlockViewer` — code blocks render natively without a viewer plugin.

#### Editor

```tsx widget
import { pluginCodeBlock, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => (
  // @essential-start
  <RicosEditor placeholder={'Add a code block...'} plugins={[pluginCodeBlock()]} />
  // @essential-end
);
```

#### Viewer

```tsx widget
import '@wix/ricos/css/ricos-viewer.global.css';
import { type RicosDocument, RicosViewer } from '@wix/ricos';
import React from 'react';

const content = {
  nodes: [
    {
      type: 'CODE_BLOCK',
      id: 'example-id',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: "console.log('Hello, world!');\nconsole.log(new Date);",
          },
        },
      ],
      codeBlockData: {},
    },
  ],
} as RicosDocument;

// @essential-start
export default () => <RicosViewer content={content} />;
// @essential-end
```

### Collapsible List Plugin

CollapsibleList plugin allows the user to add super cool collapsible list!

#### Usage

##### Editor

```tsx widget
import { pluginCollapsibleList, RicosEditor } from '@wix/ricos';
import React from 'react';
import { useDevContent } from 'ricos-dev-utils';

export default () => {
  const [content, setContent] = useDevContent();

  return (
    // @essential-start
    <RicosEditor
      placeholder={'Add collapsible list'}
      plugins={[pluginCollapsibleList()]}
      content={content}
      onChange={setContent}
    />
    // @essential-end
  );
};
```

##### Viewer

```tsx widget
import '@wix/ricos/css/plugin-collapsible-list-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginCollapsibleListViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginCollapsibleListViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'COLLAPSIBLE_LIST',
      id: 'b0fu917',
      nodes: [
        {
          type: 'COLLAPSIBLE_ITEM',
          id: 'mm8oh20',
          nodes: [
            {
              type: 'COLLAPSIBLE_ITEM_TITLE',
              id: '03qwh21',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'gtvb622',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: 'gqvb622',
                      textData: {
                        text: 'Collapsible list title',
                      },
                    },
                  ],
                },
              ],
            },
            {
              type: 'COLLAPSIBLE_ITEM_BODY',
              id: 'p0wqu23',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 't4jcv24',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: 'gtvb62b',
                      textData: {
                        text: 'Collapsible list body',
                      },
                    },
                  ],
                },
              ],
            },
          ],
        },
      ],
      collapsibleListData: {
        containerData: {
          alignment: 'CENTER',
          textWrap: true,
        },
        expandOnlyOne: false,
        initialExpandedItems: 'FIRST',
        direction: 'LTR',
      },
    },
  ],
} as RichContent;
```

#### CollapsibleList Config API

##### isQapageData configuration to customize CollapsibleList appearance in search results as an FAQ.

```jsx
isQapageData?: boolean;
```

#### Currently supported plugins

For now, these are the plugins that are supported in the collapsible list and can be added when editing a cell:

1. Text color
2. Text highlight
3. Indent
4. Line spacing
5. Link
6. Image
7. Video
8. Gif
9. Emoji
10. File upload
11. Divider
12. Code block
13. Unsupported blocks
14. Spoiler


### Divider Plugin

The Divider plugin allows users to insert horizontal dividers between blocks.

Use the exported `DIVIDER_BUTTONS` constant to customize which divider toolbar buttons are visible.

#### Editor

```tsx widget
import { pluginDivider, type RicosDocument, RicosEditor } from '@wix/ricos';
import React from 'react';

const content: RicosDocument = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'intro',
      nodes: [{ type: 'TEXT', id: '', textData: { text: 'Chapter 1' } }],
    },
    {
      type: 'DIVIDER',
      id: 'd1',
      dividerData: {
        lineStyle: 'SINGLE',
        width: 'LARGE',
        alignment: 'CENTER',
      },
    },
    {
      type: 'PARAGRAPH',
      id: 'body',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          textData: { text: 'Try clicking the divider to change its style.' },
        },
      ],
    },
  ],
};

// @essential-start
export default () => <RicosEditor initialContent={content} plugins={[pluginDivider()]} />;
// @essential-end
```

#### Viewer

```tsx widget
import '@wix/ricos/css/plugin-divider-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import { pluginDividerViewer, type RicosDocument, RicosViewer } from '@wix/ricos';
import React from 'react';

const content: RicosDocument = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'intro',
      nodes: [{ type: 'TEXT', id: '', textData: { text: 'Chapter 1: Introduction' } }],
    },
    {
      type: 'DIVIDER',
      id: 'd1',
      dividerData: {
        lineStyle: 'SINGLE',
        width: 'LARGE',
        alignment: 'CENTER',
      },
    },
    {
      type: 'PARAGRAPH',
      id: 'body',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: 'Welcome to the introductory chapter. Here we will discuss the basics of our topic.',
          },
        },
      ],
    },
  ],
};

// @essential-start
export default () => <RicosViewer content={content} plugins={[pluginDividerViewer()]} />;
// @essential-end
```

### Emoji Plugin

The Emoji plugin lets users insert emoji characters into the editor. Emojis are inserted as plain Unicode text — no special node type or decoration is added.

> **Note:** There is no `pluginEmojiViewer` — emojis are stored as plain Unicode text in the document and render naturally without any viewer plugin.

#### Editor

```tsx widget
import { pluginEmoji, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosEditor placeholder={'Add an emoji'} plugins={[pluginEmoji()]} />;
  // @essential-end
};
```

##### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">EmojiPluginEditorConfig</span>

Configuration for the Emoji editor plugin. Currently has no options — pass none.  Note: There is no corresponding `pluginEmojiViewer` — emojis are stored as plain Unicode text in the document and render naturally without any viewer plugin.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> EmojiPluginEditorConfig</code>

<dl class="pl-4 border-l-2 border-slate-200">

</dl>

### File Upload Plugin

A slick UI component for uploading, viewing and downloading files.

#### Usage

##### Editor

```tsx widget
import { pluginFileUpload, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosEditor placeholder={'Upload a file...'} plugins={[pluginFileUpload()]} />;
  // @essential-end
};
```

##### Viewer

```tsx widget
import '@wix/ricos/css/plugin-file-upload-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginFileUploadViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginFileUploadViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'FILE',
      id: 'c4fuo30',
      fileData: {
        src: {
          url: 'https://static.wixstatic.com/media/feac6b9cf32640d3ac325c905f2b3786.jpg',
        },
        name: 'image.jpg',
        type: 'jpg',
        size: 150000,
      },
    },
  ],
} as RichContent;
```

#### Config

##### `upload functions`

```jsx
handleFileUpload: (files: File[], updateEntity: UpdateEntityFunction) => void
handleFileSelection: (updateEntity: UpdateEntityFunction) => void
```

`handleFileUpload` is a function provided to handle media uploads from the native file input. <br>
`handleFileSelection` is a function provided to handle media uploads from a custom file input.

In both functions when the upload phase is done the `updateEntity` callback should be called with an object containing the data, error (upon faliure) and index (for multiple file uploads).

```jsx

UpdateEntityFunction<T>: (data: T, error: MediaUploadError, index: number) => void

data?: {
  name: string;
  type: string;
  url?: string;
  id?: string;
  privacy?: 'public' | 'private';
}

MediaUploadError?: {
  msg?: string | JSX.Element;
  key?: MediaUploadErrorKey;
  args?: Record<string, string | number>;
}
```

##### `privacy`

Access control of the uploaded file: either 'public' or 'private'.
Whatever value you set here, will be stored in the data of an uploaded file.
This parameter is sent into `resolveFileUrl`, and you can use it when you customize your own resolver.

##### `modify accepted file types`

```jsx
     accept: '*',
```

##### `File extension to file type mapper`

A utility function that maps file extension to file type. <br>
Usage: <br>

```jsx
import { fileExtensionToType, FileTypes } from '@wix/ricos';

FileTypes: enum {
  IMAGE = 1,
  VIDEO,
  WORD,
  EXCEL,
  ARCHIVE,
  PDF,
  POWERPOINT,
  AUDIO,
  MISC,
}

fileExtensionToType: (extension: string) => FileTypes

```

### Custom Font Size

Font Size enables inline customization of the font size

#### Usage

Adding the font size dropdown component is as simple as adding a button to the new formatting toolbar, however its not added by default. <br>
To add the button pass `FORMATTING_BUTTONS.FONT_SIZE` to the array of desktop buttons, note that its only available on desktop.

##### Editor

```tsx widget
import { FORMATTING_BUTTONS, fromPlainText, RicosEditor, ToolbarType } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosEditor
      content={fromPlainText('Highlight anywhere in the text and set the font size !')}
      placeholder={'Type here!'}
      toolbarSettings={{
        getToolbarSettings: ({ textButtons }) =>
          textButtons
            ? [
                {
                  name: ToolbarType.INLINE,
                  getButtons: () => ({
                    desktop: [FORMATTING_BUTTONS.FONT_SIZE],
                    mobile: {
                      ios: [
                        FORMATTING_BUTTONS.HEADINGS,
                        '|',
                        ...textButtons.mobile.filter(b => b !== FORMATTING_BUTTONS.TITLE),
                      ],
                      android: [
                        FORMATTING_BUTTONS.HEADINGS,
                        '|',
                        ...textButtons.mobile.filter(b => b !== FORMATTING_BUTTONS.TITLE),
                      ],
                    },
                  }),
                },
                {
                  name: ToolbarType.FORMATTING,
                  getButtons: () => ({
                    desktop: [FORMATTING_BUTTONS.FONT_SIZE],
                    mobile: {
                      ios: [
                        FORMATTING_BUTTONS.HEADINGS,
                        '|',
                        ...textButtons.mobile.filter(b => b !== FORMATTING_BUTTONS.TITLE),
                      ],
                      android: [
                        FORMATTING_BUTTONS.HEADINGS,
                        '|',
                        ...textButtons.mobile.filter(b => b !== FORMATTING_BUTTONS.TITLE),
                      ],
                    },
                  }),
                },
              ]
            : [],
      }}
    />
    // @essential-end
  );
};
```

### Gallery Plugin

What's better than a picture? A lot of pictures ! <br>
Let's push even higher with tons of customizable layouts to explore !

#### Usage

##### Editor

```tsx widget
import { pluginGallery, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosEditor placeholder={'Add your gallery...'} plugins={[pluginGallery()]} />;
  // @essential-end
};
```

##### Viewer

```tsx widget
import '@wix/ricos/css/plugin-gallery-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginGalleryViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginGalleryViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: '01w9f50',
    },
    {
      type: 'GALLERY',
      id: 'ngwzz49',
      galleryData: {
        containerData: {
          width: {
            size: 'CONTENT',
          },
          alignment: 'CENTER',
          spoiler: {},
          height: {},
          textWrap: true,
        },
        items: [
          {
            image: {
              media: {
                src: {
                  url: '8bb438_36726a2d14ec44ee9edc5693bade1092.jpg',
                },
                width: 3648,
                height: 5472,
              },
            },
          },
          {
            image: {
              media: {
                src: {
                  url: '8bb438_6bbf8e82fe8f4b79b6e03ee79b66fd6a.jpg',
                },
                width: 1920,
                height: 1280,
              },
            },
          },
          {
            image: {
              media: {
                src: {
                  url: '8bb438_8307fc32bdf4455ab3033c542da4c6c7.jpg',
                },
                width: 5600,
                height: 3733,
              },
            },
          },
        ],
        options: {
          layout: {
            type: 'GRID',
            horizontalScroll: false,
            orientation: 'COLUMNS',
            numberOfColumns: 3,
          },
          item: {
            ratio: 1,
            crop: 'FILL',
          },
          thumbnails: {
            placement: 'NONE',
          },
        },
      },
    },
    {
      type: 'PARAGRAPH',
      id: 'rb3qi51',
    },
  ],
} as RichContent;
```

#### Config

##### `upload functions`

```jsx
handleFileUpload: (files: File[], updateEntity: UpdateEntityFunction) => void
handleFileSelection: (updateEntity: UpdateEntityFunction) => void
```

`handleFileUpload` is a function provided to handle media uploads from the native file input. <br>
`handleFileSelection` is a function provided to handle media uploads from a custom file input.

In both functions when the upload phase is done the `updateEntity` callback should be called with an object containing the data, error (upon faliure) and index (for multiple file uploads).

```jsx

UpdateEntityFunction<T>: ({ data: T, error: MediaUploadError, index: number }) => void

data?: {
  id: string,
  original_file_name: string,
  file_name: string,
  width: number,
  height: number,
}

MediaUploadError?: {
  msg?: string | JSX.Element;
  key?: MediaUploadErrorKey;
  args?: Record<string, string | number>;
}
```

##### `Scrolling Element`

The `scrollingElement` is required for the gallery to work properly, should return the element containing the editor\gallery in the page. <br>
If not provided it would fall back to `document.body`.

```jsx
scrollingElement: () => JSX.Element;
```

##### `modify default config`

<div class="w-full rounded border-l-4 pt-3 mb-6 pl-[14px] pr-[18px] text-[15px] leading-[21px] [&amp;>:not(a)]:text-slate-950 flex flex-col gap-1 bg-blue-50 border-blue-300">
  <p class="m-0 text-slate-950"><strong>Note:</strong> To enable <strong>video</strong> use <code>accept: 'image/*,video/*'</code>.</p>
</div>

```jsx
accept: 'image/* | video/*' | 'image/*,video/*',
defaultData: {
  config: {
    size: 'small' | 'fullWidth' |'content',
    alignment: 'left' | 'right' | 'center',
    spacing: number,
    disableExpand: boolean,
  },
},
```

Default value:

```jsx
accept: '.jpg,.png,.gif,.jpeg,.jpe,.jfif,.bmp,.heic,.heif,.tfif,.tif,.webp',
config: {
  size: 'content',
  alignment: 'center',
  spacing: 0,
},
```

##### `Viewer Config`

```jsx
    disableExpand: boolean,
```

### Giphy Plugin

The Giphy plugin allows users to search for and embed GIFs from the [Giphy](https://giphy.com) library.

#### Editor

```tsx widget
import { pluginGiphy, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosEditor placeholder={'Add A GIF to get the party started!'} plugins={[pluginGiphy()]} />
    // @essential-end
  );
};
```

##### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">GiphyPluginEditorConfig</span>

Configuration for the Giphy editor plugin. Pass to `pluginGiphy()`.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> GiphyPluginEditorConfig</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>getGiphyUrl</strong><span class="text-slate-400">?</span>: (path: 'search' | 'trending') =&gt; string | undefined</code></dt>
<dd><p>Override the Giphy proxy URL. Called with `'search'` or `'trending'` to get the endpoint URL for the corresponding Giphy API operation. Return `undefined` to fall back to the default proxy.</p></dd>
</dl>

#### Viewer

```tsx widget
import '@wix/ricos/css/plugin-giphy-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginGiphyViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginGiphyViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'GIF',
      id: 'a00xy33',
      gifData: {
        containerData: {
          width: {
            size: 'CONTENT',
          },
          alignment: 'CENTER',
          textWrap: true,
        },
        original: {
          gif: 'https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExYng4MjIyeXliYWozODB3NmxncDhwcXMwNjcxZ2dkZHJ5ZjBnc2dpZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/kYNVwkyB3jkauFJrZA/giphy.webp',
        },
        height: 480,
        width: 480,
      },
    },
  ],
} as RichContent;
```

##### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">GiphyPluginViewerConfig</span>

Configuration for the Giphy viewer plugin. Currently has no options — pass none.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> GiphyPluginViewerConfig</code>

<dl class="pl-4 border-l-2 border-slate-200">

</dl>

### Hashtag Plugin

The Hashtag plugin highlights `#hashtag` text in content. Configure the viewer to make hashtags clickable links to a search page.

> **Note:** Hashtags are not stored as a content decoration. The plugin works by scanning text nodes with a regex at render time, so any text matching the hashtag pattern is automatically highlighted.

#### Editor

```tsx widget
import { pluginHashtag, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosEditor placeholder={'Add hashtag by typing # first'} plugins={[pluginHashtag()]} />;
  // @essential-end
};
```

##### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">HashtagPluginEditorConfig</span>

Configuration for the Hashtag editor plugin. Currently has no options — pass none.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> HashtagPluginEditorConfig</code>

<dl class="pl-4 border-l-2 border-slate-200">

</dl>

#### Viewer

```tsx widget
import '@wix/ricos/css/plugin-hashtag-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginHashtagViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosViewer
      content={content}
      plugins={[
        pluginHashtagViewer({
          onClick: () => {
            alert('This is example of optional Hashtag click handler');
          },
        }),
      ]}
    />
    // @essential-end
  );
};

const content = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'ao9ao137',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: 'Some simple #hashtag example',
          },
        },
      ],
    },
  ],
} as RichContent;
```

##### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">HashtagPluginViewerConfig</span>

Configuration for the Hashtag viewer plugin. Pass to `pluginHashtagViewer()`.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> HashtagPluginViewerConfig</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>createHref</strong><span class="text-slate-400">?</span>: (text: string) =&gt; string</code></dt>
<dd><p>Converts hashtag text to a URL. When provided, hashtags are rendered as anchor links. Receives the hashtag text without the `#` prefix.</p></dd>
<dt><code><strong>onClick</strong><span class="text-slate-400">?</span>: (event: React.MouseEvent&lt;HTMLElement&gt; | React.KeyboardEvent&lt;HTMLElement&gt;, text: string) =&gt; void</code></dt>
<dd><p>Callback fired when a user clicks (or presses Enter/Space on) a hashtag in the viewer. Receives the click event and the hashtag text.</p></dd>
</dl>

### Headings Plugin

The Headings plugin adds a headings dropdown to the toolbar, letting users set heading levels (H1–H6) on text blocks.

> **Note:** The viewer renders headings natively without requiring a plugin — only the editor needs `pluginHeadings()`.

#### Editor

```tsx widget
import { fromPlainText, pluginHeadings, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => (
  // @essential-start
  <RicosEditor
    initialContent={fromPlainText('Type some text, then use the headings dropdown to set H1–H6.')}
    plugins={[pluginHeadings()]}
  />
  // @essential-end
);
```

##### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">HeadingsPluginEditorConfig</span>

Configuration for the Headings editor plugin. Pass to `pluginHeadings()`.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> HeadingsPluginEditorConfig</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>customHeadings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#headingtype"><span class="font-mono">HeadingType</span></a>[]</code></dt>
<dd><p>Restricts which heading levels appear in the headings dropdown.</p>
<p>Default: <code>All headings are shown: `['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P']`</code></p></dd>
<dt><code><strong>allowHeadingCustomization</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, each heading entry in the dropdown shows a preview of its current font size and family (resolved from the theme), and clicking opens a sub-panel for in-content style customization.</p>
<p>Default: <code>false</code></p></dd>
</dl>

### Html Plugin

HTML plugin enables users to embed content in an iframe (either source code or by URL)

#### Usage

##### Editor

```tsx widget
import { pluginHtml, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosEditor placeholder={'Type here'} plugins={[pluginHtml()]} />;
  // @essential-end
};
```

##### Viewer

```tsx widget
import '@wix/ricos/css/plugin-html-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginHtmlViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginHtmlViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'HTML',
      id: 'zpkdz19',
      htmlData: {
        containerData: {
          width: {
            custom: '550',
          },
          alignment: 'CENTER',
          height: {
            custom: '550',
          },
        },
        url: 'https://en.wikipedia.org/wiki/Luxembourg',
        source: 'HTML',
      },
    },
  ],
} as RichContent;
```

#### Html Config API

##### `maxWidth`

The maximum component width in pixels. note: this value is dynamically limited by the editor boundaries

```jsx
maxWidth?: number;
```

Default value:

```jsx
940;
```

##### `minWidth`

The minimum component width in pixels

```jsx
minWidth?: number;
```

Default value:

```jsx
35;
```

##### `width`

The initial component width in pixels

```jsx
width?: number;
```

Default value:

```jsx
740;
```

##### `minHeight`

The minimum component height in pixels

```jsx
minHeight?: number;
```

Default value:

```jsx
35;
```

##### `maxHeight`

The maximum component height in pixels

```jsx
maxHeight?: number;
```

Default value:

```jsx
1200;
```

### Adsense Plugin

Adsense plugin is part of the html plugin. To use it we need to expose a button with `exposeButtons` and add the `siteDomain`:

#### Usage

##### Editor

```tsx widget
import { htmlButtonsTypes, pluginHtml, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosEditor
      placeholder={'Type here'}
      plugins={[
        pluginHtml({
          exposeButtons: [htmlButtonsTypes.adsense],
          siteDomain: 'https://dana.wixsite.com/mysite',
        }),
      ]}
    />
    // @essential-end
  );
};
```

##### `siteDomain` **Required**

Adsense will not work with out the siteDomain

```jsx
siteDomain?: string;
```

##### `exposeButtons` **Required**

The buttons exposed by the plugin

```jsx
exposeButtons?: array;
```

Example value:

```jsx
[htmlButtonsTypes.html, htmlButtonsTypes.adsense];
```

### Image Plugin

Picture is Worth a Thousand Words!

#### Usage

##### Editor

```tsx widget
import { pluginImage, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosEditor placeholder={'Add an image..'} plugins={[pluginImage()]} />;
  // @essential-end
};
```

##### Viewer

```tsx widget
import '@wix/ricos/css/plugin-image-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginImageViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginImageViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'IMAGE',
      id: 'dfkcg1',
      nodes: [
        {
          type: 'CAPTION',
          id: '9klys14',
          nodes: [
            {
              type: 'TEXT',
              id: '',
              nodes: [],
              textData: {
                text: 'Beautiful sunset.',
                decorations: [],
              },
            },
          ],
          captionData: {},
        },
      ],
      imageData: {
        containerData: {
          width: {
            size: 'CONTENT',
          },
          alignment: 'CENTER',
          textWrap: true,
        },
        image: {
          src: {
            url: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_1280.jpg',
          },
          width: 5120,
          height: 3413,
        },
        altText: 'A tree during sunset.',
        caption: 'Beautiful sunset.',
      },
    },
  ],
} as RichContent;
```

#### Config

##### `upload functions`

```jsx
handleFileUpload: (files: File[], updateEntity: UpdateEntityFunction) => void
handleFileSelection: (updateEntity: UpdateEntityFunction) => void
```

`handleFileUpload` is a function provided to handle media uploads from the native file input. <br>
`handleFileSelection` is a function provided to handle media uploads from a custom file input.

In both functions when the upload phase is done the `updateEntity` callback should be called with an object containing the data, error (upon failure) and index (for multiple file uploads).

```jsx
UpdateEntityFunction<T>: ({ data: T, error: MediaUploadError, index: number }) => void

data?: {
  id: string,
  original_file_name: string,
  file_name: string,
  width: number,
  height: number,
}

MediaUploadError?: {
  msg?: string | JSX.Element;
  key?: MediaUploadErrorKey;
  args?: Record<string, string | number>;
}
```

##### `modify default config`

```jsx
     defaultData: {
       config: {
         size: 'small' | 'fullWidth' |'content',
         alignment: 'left' | 'right' | 'center',
         showTitle: boolean,
         showDescription: boolean,
       },
     },
```

Default value:

```jsx
       config: {
         size: 'content',
         alignment: 'center',
         showTitle: true,
         showDescription: true,
       },
```

##### `imageProps`

An additional props to be added to the `<img />`

##### `createGalleryForMultipleImages`

```jsx
createGalleryForMultipleImages: boolean;
```

When enabled, multiple images uploaded at once will be added as a gallery.

##### `Viewer Config`

```jsx
    disableExpand: boolean,
    disableTransition: boolean,
```

| Param             | Type    | Description                                          |
| ----------------- | ------- | ---------------------------------------------------- |
| disableExpand     | boolean | -                                                    |
| disableTransition | boolean | disables wow-image transition from low quality image |

### Link Plugin

Link plugin allow the user to add links

#### Usage

##### Editor

```tsx widget
import { pluginLink, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosEditor placeholder={'Add a link..'} plugins={[pluginLink()]} />;
  // @essential-end
};
```

##### Viewer

```tsx widget
import '@wix/ricos/css/plugin-link-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginLinkViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginLinkViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'nlw03200',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: 'Read ',
          },
        },
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: 'here',
            decorations: [
              {
                type: 'LINK',
                linkData: {
                  link: {
                    url: 'https://en.wikipedia.org/wiki/Quinoa',
                    target: 'BLANK',
                    rel: {
                      noreferrer: true,
                    },
                  },
                },
              },
              {
                type: 'UNDERLINE',
              },
            ],
          },
        },
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: ' for more information.',
          },
        },
      ],
    },
  ],
} as RichContent;
```

#### Link Config API

##### `Link Preview`

The Link Preview Plugin is implemented within the Link Plugin,
and is activated once the Link Plugin Settings object contains preview field.
For further information check the PreviewPlugin page.

Default value:

```jsx
none;
```

#### Anchor (Link To Section)

Anchor option allows users to navigate to a certain section of the website

##### `linkTypes`

Supported Links types + pass configuration to those types

Default value:

```jsx
{
  anchor: true;
}
```

##### `blockPreview`

Anchor configuration to customize the preview elements shown in the anchor panel.

example:

```jsx
{
  linkTypes: {
      anchor: {
        blockPreview: ({ type, data, text }) => {
          console.log({ type, data, text });
          const blockPreview = { thumbnail: <div>bla1</div>, type: 'bla2', content: 'bla3' };
          return blockPreview;
        },
      },
    },
}
```

`type` - block plugin type e.g. "wix-draft-plugin-image", "wix-draft-plugin-video", unstyled...
`data` - block entity data e.g. src, config...
`text` - block text

##### `disableAutoLink`

Disable auto linkify links.

Default value:

```jsx
disableAutoLink: false;
```

##### Custom Link

Allows users to render custom link panel, save their data, and get it by the `onClick` event.

##### `onLinkAdd`

```jsx
onLinkAdd?: (CustomLinkData: any, saveData: (CustomLinkData: any) => void ) => void
```

`onLinkAdd` is a function provided to give you the option to customize our default link behavior,
you can render your own link panel, and call `saveData` function to save your changes.<br>
To get your saved data, you will need to use `onClick` event in the viewer, check it out below.<br>

#### Viewer Config

##### `onClick`

```jsx
onClick?: (event: React.MouseEvent<HTMLElement>, data: string | CustomLinkData) => void;
```

`data` can be resulted of two types:

1. `string` - the url for the following link.
2. `CustomLinkData: any` - custom link data, can be any data you wish to save.

Default value:

```jsx
none;
```

### LinkPreview Plugin

Link Preview plugin allow the user to add a link preview.
The Link Preview Plugin is implemented within the Link Plugin, and is activated once the user insert a link and press 'Enter' key.
Link Preview with inline-embed: for this Usage, html plugin should also be installed.

#### Usage

##### Editor

```tsx widget
import { fromPlainText, pluginLink, pluginLinkPreview, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosEditor
      content={fromPlainText('Add an Link Preview - write a link and press enter key')}
      placeholder={'Type here..'}
      plugins={[
        pluginLink(),
        pluginLinkPreview({
          fetchData: () =>
            Promise.resolve({
              title: 'A mock title',
              description: 'A mock description',
              thumbnail_url:
                'https://image.insider.com/5de5784979d757159d0b6838?width=1100&format=jpeg&auto=webp',
              provider_url: 'www.mockUrl.com',
            }),
        }),
      ]}
    />
    // @essential-end
  );
};
```

##### Viewer

```tsx widget
import '@wix/ricos/css/plugin-link-preview-viewer.global.css';
import '@wix/ricos/css/plugin-link-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginLinkPreviewViewer, pluginLinkViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosViewer content={content} plugins={[pluginLinkViewer(), pluginLinkPreviewViewer()]} />
    // @essential-end
  );
};

const content = {
  nodes: [
    {
      type: 'LINK_PREVIEW',
      id: '40335110',
      linkPreviewData: {
        containerData: {
          width: {
            size: 'CONTENT',
          },
          alignment: 'CENTER',
        },
        link: {
          url: 'https://www.popsci.com/environment/do-dogs-dream/',
          target: 'BLANK',
        },
        title: 'Do dogs dream?',
        thumbnailUrl:
          'https://image.insider.com/5de5784979d757159d0b6838?width=1100&format=jpeg&auto=webp',
        description: 'The answer might make you appreciate your pup even more.',
      },
    },
  ],
} as RichContent;
```

#### Link Preview Config API

##### `enableEmbed`

Allows to display provider embed if exist (can be true/false/[LinkPreviewProviders.Twitter, LinkPreviewProviders.Instagram,..]

```jsx
enableEmbed?: one of boolean, array;
```

Default value:

```jsx
true;
```

##### `enableLinkPreview`

Allows to display link preview if exist

```jsx
enableLinkPreview?: boolean;
```

Default value:

```jsx
true;
```

##### `fetchData`

A func that gets url and returns an object with title, image_url, description (optional) that related to the given url

```jsx
fetchData?: function;
```

Default value:

```jsx
none;
```

##### `exposeEmbedButtons`

> Note: LinkPreviewProviders.youTube is gonna be deprecate. To use youtube plugin you should consume video plugin and expose youtube button. See docs here https://ricos.js.org/docs/plugins_api/VideoPlugin#youtube-plugin

Which embed buttons will display - array with LinkPreviewProvider types [LinkPreviewProviders.Instagram, LinkPreviewProviders.Twitter, ...].
For provide buttons type you should import LinkPreviewProvider.

```jsx
LinkPreviewProviders?: array;
```

Default value:

```jsx
[];
```

### Spoiler Plugin

The Spoiler plugin lets users hide content behind a reveal button. It works on both text (via the formatting toolbar) and plugin blocks (images, galleries, videos — via the plugin settings modal).

#### Editor

```tsx widget
import {
  fromPlainText,
  pluginImage,
  pluginSpoiler,
  pluginVideo,
  RicosEditor,
} from '@wix/ricos';
import React from 'react';

export default () => (
  // @essential-start
  <RicosEditor
    initialContent={fromPlainText('Add content and mark it as a spoiler!')}
    plugins={[pluginSpoiler(), pluginImage(), pluginVideo()]}
  />
  // @essential-end
);
```

#### Viewer

```tsx widget
import '@wix/ricos/css/plugin-spoiler-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import { pluginSpoilerViewer, type RicosDocument, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginSpoilerViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'axdjn4',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: 'Spoiler',
            decorations: [
              {
                type: 'SPOILER',
              },
              {
                type: 'COLOR',
                colorData: {},
              },
            ],
          },
        },
      ],
    },
  ],
} as RicosDocument;
```

### Table Plugin

The Table plugin lets users insert and edit tables. Cells can contain other plugin nodes (text, links, images, etc.).

Use the exported `TABLE_BUTTONS` constant to customize which table toolbar buttons appear.

#### Editor

```tsx widget
import { pluginTable, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => (
  // @essential-start
  <RicosEditor placeholder={'Use the toolbar to insert a table...'} plugins={[pluginTable()]} />
  // @essential-end
);
```

#### Viewer

```tsx widget
import '@wix/ricos/css/plugin-table-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import { pluginTableViewer, type RicosDocument, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginTableViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'TABLE',
      id: '9g36s18',
      nodes: [
        {
          type: 'TABLE_ROW',
          id: 'r7gdr21',
          nodes: [
            {
              type: 'TABLE_CELL',
              id: '3pzdu75',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'fon0l76',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Name',
                        decorations: [
                          {
                            type: 'BOLD',
                            fontWeightValue: 700,
                          },
                        ],
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: '9wuv079',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'idtbo80',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Position',
                        decorations: [
                          {
                            type: 'BOLD',
                            fontWeightValue: 700,
                          },
                        ],
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'tqfv684',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'mnhrp85',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Location',
                        decorations: [
                          {
                            type: 'BOLD',
                            fontWeightValue: 700,
                          },
                        ],
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'zwrg390',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: '6j2gt91',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Years of Service',
                        decorations: [
                          {
                            type: 'BOLD',
                            fontWeightValue: 700,
                          },
                        ],
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
          ],
        },
        {
          type: 'TABLE_ROW',
          id: 'duvpp30',
          nodes: [
            {
              type: 'TABLE_CELL',
              id: 'z6tty101',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: '83fk8102',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'John Doe',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'eri6f129',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'salmd130',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Software Engineer',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'wxca7138',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'ua2un139',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'New York',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'f2j0437',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'ne6z638',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: '5',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
          ],
        },
        {
          type: 'TABLE_ROW',
          id: 'y85h939',
          nodes: [
            {
              type: 'TABLE_CELL',
              id: '66c76157',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'l2h6h158',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Jane Smith',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'bsuzu169',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'cdjar170',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Product Manager',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'qet97182',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'ans0s183',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'San Francisco',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: '1ce1h46',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'astfs47',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: '3',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
          ],
        },
        {
          type: 'TABLE_ROW',
          id: 'vld5n48',
          nodes: [
            {
              type: 'TABLE_CELL',
              id: 'k1v2b236',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'jotik237',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Sam Johnson',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: '5zvv3254',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'vgscl255',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'UX Designer',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'ol6t7273',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'kj82q274',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Remote',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'ipx9955',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'nove956',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: '2',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
          ],
        },
        {
          type: 'TABLE_ROW',
          id: '1c81x57',
          nodes: [
            {
              type: 'TABLE_CELL',
              id: '672s3294',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: '9qtxt295',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Emily Davis',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: '9t9wi315',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'yfnm1316',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Data Scientist',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'iav9i337',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: '5edbz338',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: 'Boston',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
            {
              type: 'TABLE_CELL',
              id: 'ir2bq64',
              nodes: [
                {
                  type: 'PARAGRAPH',
                  id: 'rdvbi65',
                  nodes: [
                    {
                      type: 'TEXT',
                      id: '',
                      textData: {
                        text: '4',
                      },
                    },
                  ],
                },
              ],
              tableCellData: {},
            },
          ],
        },
      ],
      tableData: {
        dimensions: {
          colsWidthRatio: [139, 139, 139, 139],
          rowsHeight: [47, 47, 47, 47, 47],
          colsMinWidth: [120, 120, 120, 120],
        },
      },
    },
    {
      type: 'PARAGRAPH',
      id: 'lvoqc20',
    },
  ],
} as RicosDocument;
```

### Text Color and Highlight Plugins

One (or actually Two) of the most basic features in text editing!
Modify text color or highlight the important stuff.

#### Usage

##### Editor

```tsx widget
import { fromPlainText, pluginTextColor, pluginTextHighlight, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosEditor
      placeholder={'Add text...'}
      content={fromPlainText('Color & Highlight text as you please!')}
      plugins={[pluginTextColor(), pluginTextHighlight()]}
    />
    // @essential-end
  );
};
```

##### Viewer

```tsx widget
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginTextColorViewer, pluginTextHighlightViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosViewer
      content={content}
      plugins={[pluginTextColorViewer(), pluginTextHighlightViewer()]}
    />
    // @essential-end
  );
};

const content = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'y549n1',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: 'Fun',
            decorations: [
              {
                type: 'BOLD',
                fontWeightValue: 700,
              },
            ],
          },
        },
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: ' ',
          },
        },
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: 'text',
            decorations: [
              {
                type: 'ITALIC',
                italicData: true,
              },
            ],
          },
        },
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: ' ',
          },
        },
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: 'with',
            decorations: [
              {
                type: 'UNDERLINE',
              },
            ],
          },
        },
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: ' ',
          },
        },
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: 'decorations',
            decorations: [
              {
                type: 'COLOR',
                colorData: {
                  background: '#0141AA',
                  foreground: '#bf695c',
                },
              },
            ],
          },
        },
        {
          type: 'TEXT',
          id: '',
          textData: {
            text: '!',
          },
        },
      ],
    },
  ],
} as RichContent;
```

#### Config

Text Color and Text Highlight have exactly the same config but both require it to be passed to them separately for flexibility.

| setting key               | description                                                                            | default value                             | is required? | editor/viewer config |
| ------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------- | ------------ | -------------------- |
| `colorScheme`             | custom-style to hex-color map: `{ color1: { color: '#fff', index: 0 }, ... }`          | none (DEFAULT_PALETTE is used by default) | No           | editor               |
| `styleSelectionPredicate` | a function that tells the plugin which inline styles are relevant: `string => boolean` | `style => isHexColor(style)`              | No           | both                 |
| `customStyleFn` (1)       | a function that converts an inline-style to CSS style: `DraftInlineStyle => object`    | DEFAULT_STYLE_FN_DRAFT                    | No           | editor               |
| `customStyleFn` (2)       | a function that converts an inline-style to CSS style: `string => object`              | DEFAULT_STYLE_FN                          | No           | viewer               |
| `onColorAdded`            | a handler called when a custom color is added                                          | none                                      | No           | editor               |
| `getUserColors`           | a function that returns user-defined custom colors                                     | none                                      | No           | editor               |

### Video Plugin

If a Picture is Worth a Thousand Words, Then Is a Video Worth a Million?

#### Usage

##### Editor

```tsx widget
import { pluginVideo, RicosEditor, videoButtonsTypes } from '@wix/ricos';
import React from 'react';

export default () => {
  return (
    // @essential-start
    <RicosEditor
      placeholder={'Add an video..'}
      plugins={[
        pluginVideo({
          exposeButtons: [videoButtonsTypes.video, videoButtonsTypes.youTube],
        }),
      ]}
    />
    // @essential-end
  );
};
```

##### Viewer

```tsx widget
import '@wix/ricos/css/plugin-video-viewer.global.css';
import '@wix/ricos/css/ricos-viewer.global.css';
import type { RichContent } from '@wix/ricos';
import { pluginVideoViewer, RicosViewer } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosViewer content={content} plugins={[pluginVideoViewer()]} />;
  // @essential-end
};

const content = {
  nodes: [
    {
      type: 'VIDEO',
      id: 'g5bh230',
      videoData: {
        containerData: {
          width: {
            size: 'CONTENT',
          },
          alignment: 'CENTER',
        },
        video: {
          src: {
            id: 'video/11062b_a552731f40854d16a91627687fb8d1a6/1080p/mp4/file.mp4',
          },
          duration: 14.08,
        },
        thumbnail: {
          src: {
            id: 'media/11062b_a552731f40854d16a91627687fb8d1a6f000.jpg',
          },
          width: 1920,
          height: 1080,
        },
      },
    },
  ],
} as RichContent;
```

#### Video Config API

#### `Viewer disableDownload (deprecated!)`

##### disableDownload should be set in the mediaSettings of the editor.

```jsx
disableDownload?: boolean;
```

##### `getVideoUrl`

This function take the pathname and form a full URL (invoked when rendering video which has relative URL).

```jsx
getVideoUrl?: (src: { pathname: string }) => string;
```

##### `onProgress`

Callback containing

- `played` and `loaded` progress as a fraction.
- `playedSeconds` and `loadedSeconds` in seconds.
- `id` of video

```jsx
onProgress?: () => ({ played: number, playedSeconds: number, loaded: number, loadedSeconds: number, id: string });
```

##### `progressInterval`

The time between onProgress callbacks, in milliseconds (default `1000`)

```jsx
progressInterval?: number;
```

##### `toolbar`

The toolbar setting refers to the plugin functionality toolbar, it is an object which may consist of the following keys:

```jsx
toolbar?: {
  hidden?: [keynames: string];
  icons?: { keynames: string };
}
```

`hidden` - An array of toolbar button keynames to be removed from toolbar

`icons` - An object of toolbar button keynames to custom icons map (e.g \{ replace: MyCustomReplaceIcon \}, \{ InsertPluginButtonIcon: MyCustomInsertIcon \} etc...)

##### `exposeButtons`

The buttons exposed by the plugin

```jsx
exposeButtons?: array;
```

Example value:

```jsx
[videoButtonsTypes.video, videoButtonsTypes.youtube];
```

#### YouTube Plugin

YouTube toolbar button is part of the Video plugin. To use it we need to expose a button with `exposeButtons`

### Plugin Customization

As it turns out, various `RichContentEditor` consumers have different customization needs. On other hand, it is important to keep the public API clean, while providing the desired customability. In order to meet these requirements, the `RichContentEditor` exposes `config` object prop.

This document focuses on plugin customization capabilities.

#### Plugin Settings

##### Common Settings

These settings are common to all the plugins and handled at the **base** plugin level.

###### `width` and `height`

The `width` and `height` are expected to be integers. They serve as **initial** plugin component dimensions in pixels, if there are no `width` and `height` fields defined in `componentData.config` for that specific plugin entity.

###### `toolbar`

The `toolbar` setting refers to the plugin functionality toolbar, it is an object which may consist of the following keys:

| setting key | description                                                                                   | default value | is required? | editor/viewer config |
| ----------- | --------------------------------------------------------------------------------------------- | ------------- | ------------ | -------------------- |
| `hidden`    | an array of toolbar button `keynames` to be removed from toolbar                              | none          | No           | editor               |
| `icons`     | an object of toolbar button `keynames` to custom icons map: `\{ delete: MyCustomTrashIcon \}` | none          | No           | editor               |

Currently, it exposes a field `hidden` which is expected to be an array of toolbar button `keynames` to be removed from toolbar and a field `icons` which is expected to be an object of button icons to replace the existing icons.

###### `defaultData`

The `defaultData` setting enables consumers to set the default data for a plugin when it is inserted into the editor. It is an object with the same fields as the plugins entity data.

##### Button Plugin Settings

| setting key       | description                                                   | default value | is required? | editor/viewer config |
| ----------------- | ------------------------------------------------------------- | ------------- | ------------ | -------------------- |
| `activeButton`    | The index of the chosen preset button design                  | 0             | No           | both                 |
| `borderRadius`    | The border radius in pixels                                   | 0             | No           | both                 |
| `borderWidth`     | The border width in pixels                                    | 0             | No           | both                 |
| `buttonText`      | The button label                                              | `Click Me`    | No           | both                 |
| `url`             | The URL that should be redirected when clicking on the button | none          | Yes          | both                 |
| `rel`             | The relationship between the current URL and the linked URL   | `nofollow`    | Yes          | both                 |
| `target`          | Specifies where to open URL in new tab or in current tab      | `_blank`      | Yes          | both                 |
| `textColor`       | The color of button text in Hex.                              | `#FEFDFD`     | No           | both                 |
| `borderColor`     | The color of button border in Hex.                            | `#0161FF`     | No           | both                 |
| `backgroundColor` | The color of button background in Hex.                        | `#0161FF`     | No           | both                 |

##### HTML Plugin Settings

| setting key     | description                                                                                                 | default value | is required? | editor/viewer config |
| --------------- | ----------------------------------------------------------------------------------------------------------- | ------------- | ------------ | -------------------- |
| `htmlIframeSrc` | `src` attribute of the HTML `iframe` when the plugin is used in `source mode`                               | none          | Yes          | both                 |
| `maxWidth`      | the maximum component width in pixels. **note:** this value is dynamically limited by the editor boundaries | 940           | No           | both                 |
| `minWidth`      | the minimum component width in pixels                                                                       | 35            | No           | both                 |
| `maxHeight`     | the maximum component height in pixels                                                                      | 1200          | No           | both                 |
| `minHeight`     | the minimum component height in pixels                                                                      | 35            | No           | both                 |
| `width`         | the initial component width in pixels                                                                       | 740           | No           | both                 |
| `height`        | the initial component height in pixels                                                                      | 242           | No           | both                 |
| `siteDomain`    | the site domain in order for adsense to work                                                                | none          | No           | both                 |
| `exposeButtons` | controls what buttons will be exposed to open HTML modal                                                    | ['html']      | No           | editor               |

##### Hashtag Plugin Settings

| setting key  | description                                                                                                        | default value | is required? | editor/viewer config |
| ------------ | ------------------------------------------------------------------------------------------------------------------ | ------------- | ------------ | -------------------- |
| `createHref` | converter function that converts the Hashtag `#text` to URL. if provided, the hashtags will behave as anchor links | none          | No           | both                 |
| `onClick`    | optional Hashtag click handler                                                                                     | none          | No           | both                 |

##### Link Plugin Settings

| setting key | description                 | default value      | is required? | editor/viewer config |
| ----------- | --------------------------- | ------------------ | ------------ | -------------------- |
| `onClick`   | optional Link click handler | none               | No           | both                 |
| `preview`   | Link Preview settings       | none               | No           | both                 |
| `linkTypes` | Supported Links types       | \{ anchor: true \} | No           | editor               |

The Link Preview Plugin is implemented within the Link Plugin, and is activated once the Link Plugin Settings object contains `preview` field. The `preview` object structure as follows:

```js

  {
    getMetadataUrl: (query: string) => string,
    token: string,
    format: string,
  }

```

The `getMetadataUrl` should provide a Website metadata service endpoint URL for a given query URL. The `token` is a security token appended to request headers (`Authorization` header). The `format` is the expected response format. Currently the only supported format is 'oembed'.

##### Mentions Plugin Settings

| setting key                  | description                                                                                | default value | is required? | editor/viewer config |
| ---------------------------- | ------------------------------------------------------------------------------------------ | ------------- | ------------ | -------------------- |
| `getMentions`                | function that retrieves a list of suggestions according to provided search query parameter | none          | Yes          | both                 |
| `onMentionClick`             | optional Mention click handler                                                             | none          | No           | both                 |
| `onMentionHover`             | optional Mention hover handler                                                             | none          | No           | both                 |
| `getMentionLink`             | given the mention return link for it                                                       | none          | Yes          | both                 |
| `visibleItemsBeforeOverflow` | boolean how many items should be visible before overflowing                                | none          | No           | editor               |
| `zIndex`                     | override zIndex value                                                                      | 9999          | No           | editor               |

##### Giphy Plugin Settings

| setting key             | description                                                                          | default value                                              | is required? | editor/viewer config |
| ----------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------- | ------------ | -------------------- |
| `componentDataDefaults` | Default component data                                                               | `\{ config: \{ size: 'content', alignment: 'center' \} \}` | No           | editor               |
| `insertToolbars`        | Which toolbars to add insert button to                                               | `[TOOLBARS.FOOTER]`                                        | No           | editor               |
| `getGiphyUrl`           | Function that returns the URL to giphy service for fetching trending and search gifs | none                                                       | No           | editor               |

##### Image Plugin Settings

| setting key                   | description                            | default value | is required? | editor/viewer config |
| ----------------------------- | -------------------------------------- | ------------- | ------------ | -------------------- |
| `onExpand`                    | called when the user expands an image  | none          | No           | viewer               |
| `disableExpand` (deprecated!) | disables expanding images & fullscreen | false         | No           | viewer               |
| `disableExpand`               | disables expanding images & fullscreen | false         | No           | editor               |

##### Gallery Plugin Settings

| setting key                  | description                           | default value | is required? | editor/viewer config |
| ---------------------------- | ------------------------------------- | ------------- | ------------ | -------------------- |
| `onExpand`                   | called when the user expands an item  | none          | No           | viewer               |
| `disableExpand` (deprecated) | disables expanding items & fullscreen | false         | No           | viewer               |
| `disableExpand`              | disables expanding items & fullscreen | false         | No           | editor               |

##### Text Color Plugin

| setting key               | description                                                                            | default value                                | is required? | editor/viewer config |
| ------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------- | ------------ | -------------------- |
| `colorScheme`             | custom-style to hex-color map: `\{ color1: \{ color: '#fff', index: 0 \}, ... \}`      | none (DEFAULT_PALETTE is used by default)    | No           | editor               |
| `styleSelectionPredicate` | a function that tells the plugin which inline styles are relevant: `string => boolean` | `style => isHexColor(style)`                 | No           | both                 |
| `customStyleFn` (1)       | a function that converts an inline-style to CSS style: `DraftInlineStyle => object`    | DEFAULT_STYLE_FN_DRAFT                       | No           | editor               |
| `customStyleFn` (2)       | a function that converts an inline-style to CSS style: `string => object`              | DEFAULT_STYLE_FN                             | No           | viewer               |
| `selectionColor`          | selected color indicator (`string`)                                                    | `#000`                                       | No           | editor               |
| `onColorAdded`            | a handler called when a custom color is added                                          | none                                         | Yes          | editor               |
| `getUserColors`           | a function that returns user-defined custom colors                                     | none                                         | Yes          | editor               |
| `onCustomPickerToggle`    | a handler called when the Add Color button is clicked [see **Note** below for details] | modal `CustomColorPickerDialog` is displayed | No           | editor               |
| `onCustomColorPicked`     | a handler called when a custom color is picked by hue/saturation controls              | noop                                         | No           | editor               |

> **Note:** `onCustomPickerToggle` API is intended to be used when there is a need to display the `CustomColorPicker` outside the modal dialog, e.g. as a Settings panel element. The API is called with the following parameters:

- `onCustomColorPicked`(color) -- should be called when a custom color is picked by hue/saturation controls of the `CustomColorPicker`. Usually it should be wired to the `CustomColorPicker`'s _`onChange`_ prop
- `onCustomColorUpdate`(color) -- should be called when user decides to apply the selected color (e.g. `CustomColorPickerDialog` Update button)
- `onCustomColorCancel`(color) -- should be called when user decides to cancel the custom color selection (e.g. `CustomColorPickerDialog` Cancel button)
- all the `ColorPicker` props (`t`, `isMobile`, `theme`, ...etc)

> **Note**: `selectionColor` prop is deprecated, please override the `.colorPicker_button_selected::after` class `border-color` rule (theme).

### Media Plugins Upload

In this section we'll further drill into media upload and error handling in Ricos.

#### Upload Functions API

This functions should be passed to the media plugins via config \ helpers (i.e image, gallery, video , audio and file upload).

```jsx
handleFileUpload: (files: File[], updateEntity: UpdateEntityFunction) => void
handleFileSelection: (updateEntity: UpdateEntityFunction) => void
```

`handleFileUpload` is a function provided to handle media uploads from the native file input. <br>
`handleFileSelection` is a function provided to handle media uploads from a custom file input.

In both functions when the upload phase is done the `updateEntity` callback should be called with an object containing the data, error (upon faliure) and index (for multiple file uploads).

```jsx
UpdateEntityFunction<T>: ({ data: T, error: MediaUploadError, index: number }) => void
```

#### Media Plugins `data` schemes

##### Image and Gallery

```jsx
data?: {
  id: string,
  original_file_name: string,
  file_name: string,
  width: number,
  height: number,
}
```

##### Video

```jsx
data?: {
  pathname: string,
  thumbnail: {
    pathname: string,
    height: number,
    width: number,
  },
}
```

##### Audio

```jsx
data?: {
  name?: string,
  authorName?: string,
  audio: {
    src: {id?: string, url?: string},
    duration?: number,
  },
  html?:string,
}
```

##### File Upload

```jsx
data?: {
  name: string;
  type: string;
  url?: string;
  id?: string;
}
```

#### Errors

In order to take advantage of Ricos stunning error toasts and general error handling a proper `error` object of type `MediaUploadError` should be passed. <br>
Passing an error object triggers a toast indicating what caused the upload to fail as well as an indication of the error on the block. <br>
Using the `getContent` method of RicosEditor would also remove all blocks with errors from the content, thus they won't be seen on Viewer nor saved. <br>

```jsx
MediaUploadError?: {
  msg?: string | JSX.Element;
  key?: MediaUploadErrorKey;
  args?: Record<string, string | number>;
}
```

`msg` is a simple string or JSX Element to use for custom error messages to be displayed. <br>
`key` is an enum with our built in supported and localized error types. <br>
`args` are arguments needed for the built in toasts. <br>

```jsx
enum MediaUploadErrorKey {
  GENERIC,
  SIZE_LIMIT,
  QUOTA_STORAGE_VISITOR,
  QUOTA_STORAGE_OWNER,
  QUOTA_VIDEO_VISITOR,
  QUOTA_VIDEO_OWNER,
}
```

`GENERIC` a general error with no specific reason <br>
`SIZE_LIMIT` the file is too big to upload, supports an argument `maxLimit` for maximum file size indication. <br>
`QUOTA_STORAGE_VISITOR` file storage exceeded the limit (UoU). <br>
`QUOTA_STORAGE_OWNER` file storage exceeded the limit (User), supports an argument `upgradeUrl` to upgrade the site's plan. <br>
`QUOTA_VIDEO_VISITOR` video hours on the site have reached the maximum (UoU). <br>
`QUOTA_VIDEO_OWNER` video hours on the site have reached the maximum (User), supports an argument `upgradeUrl` to upgrade the site's plan. <br>

### Loadable plugins

Loadable lets you render a dynamic import as a regular component.
Loadable can help you "lazy-load" just the things that are currently needed by the user, which can dramatically improve the performance of your app.

> Note: loadable plugins have different import. For using loadable plugins (meantime for viewer only), you should import plugins from `@wix/ricos/loadable` entry.

#### Usage Example

```jsx
import { RicosViewer } from '@wix/ricos';
import { pluginImageLoadable } from '@wix/ricos/loadable';

return (
  <RicosViewer plugins={[pluginImageLoadable()]} />
);
```

#### Supported plugins

Here is the list of plugins that support loadable component:

- Audio
- Button
- Collapsible List
- Divider
- File Upload
- Gallery
- Giphy
- Html
- Image
- Link
- Link Preview
- Mentions
- Poll
- Table
- Vertical Embed
- Video

## EditorEventsContext

### `withEditorContext`

`withEditorContext` is a High-Order component, allowing to trigger `publish` event when content editing is done.

Return value is a promise with finalized content.

```ts
editorEvents: {
  publish: () => Promise<RichContent>;
}
```

#### Usage Example

```tsx widget
import { fromPlainText, RicosEditor, withEditorContext } from '@wix/ricos';
import React from 'react';

export default withEditorContext(({ editorEvents }) => {
  const [logs, setLogs] = React.useState<string[]>([]);
  const onPublish = async () => {
    // @essential-start
    const content = await editorEvents.publish();
    // @essential-end
    setLogs(prevState => [JSON.stringify(content), ...prevState]);
  };
  return (
    <>
      <RicosEditor content={fromPlainText('Sample editor content')} />
      <p style={{ margin: '20px 0' }}>
        <button
          style={{ background: '#eaeaea', padding: '4px', borderRadius: '4px' }}
          type="button"
          onClick={onPublish}
        >
          Publish
        </button>
      </p>
      <ul>
        {logs.map((log, index) => (
          <li
            key={index}
            style={{
              marginBottom: '15px',
              fontSize: '12px',
              background: '#f1f1f1',
              padding: '4px',
              borderRadius: '4px',
            }}
          >
            <code>{log}</code>
          </li>
        ))}
      </ul>
    </>
  );
});
```

## Content API

### AppEmbed Data Extractor

The AppEmbed Data Extractor provides access to AppEmbed related content regardless of its nesting level.

The `extractAppEmbedData` API accepts `RichContent` object, and exposes the available AppEmbed data as array:

```ts
 extractAppEmbedData = (content: RichContent) => {
  type: PRODUCT | EVENT | BOOKING;
  id?: string;
  url?: string;

  bookingData?: {
    durations?: string;
  }

  eventData?: {
    scheduling?: string;
    location?: string;
  }
 }[]
```

### Content Applications

While [Content Builder](#content-builder), [Content Extractor](#content-extractor) and [Content Modifier](#content-modifier) APIs are very flexible, yet they also too generic.

The `content-application` is a collection of more specific APIs which aim to reduce boilerplate for consumer.

##### `getText`

Extracts textual content of Headers & Paragraphs from a given content

```ts
import { getText } from '@wix/ricos';

const text = getText(content); // input content

console.log(text);
```

##### Output

`["This is an example content, with header 3 ", "and header 4"]`

##### `getPluginData`

Extracts data of a plugin from a given content.

###### Example

```ts
import { getPluginData } from '@wix/ricos';
import { content } from 'somewhere';

const pluginData = getPluginData(content, 'IMAGE');
console.log(pluginData);
```

###### Output

```ts
[
  {
    image: { ... },
    altText: 'alt1',
    caption: 'Cool image',
    // ...
  },
  {
    image: { ... },
    altText: 'alt2',
    caption: 'Cool image 2',
    // ...
  },
];
```

### Content Extractor

#### extract API

The Content Extractor provides access to content nodes regardless of their nesting level.

The `extract` API accepts RichContent nodes array, and exposes `Extractor<T>` that allows chaining:

```ts
interface Extractor<DT> {
  filter: (predicate: (data: DT) => boolean) => Extractor<DT>;
  map: <MT>(mapper: (data: DT) => MT) => Extractor<MT>;
  chain: <CT>(mapper: (data: DT) => Extractor<CT>) => Extractor<CT>[];
  get: () => DT[];
}
```

#### Examples

1. Extract node keys of images with invalid (empty) alt text:

```ts
import { extract } from '@wix/ricos';
...
const keys = extract(richContent.nodes)
  .filter(({ imageData }) => !!imageData && !imageData.altText)
  .map(({ key }) => key)
  .get();
```

2. Extract first image URL:

```ts
const url = extract(richContent.nodes)
  .map(({ imageData }) => imageData?.image?.src?.url || imageData?.image?.src?.custom)
  .get()[0];
```

3. Extract total image count:

```ts
const imageCount = extract(richContent.nodes)
  .filter(({ type }) => type === Node_Type.IMAGE)
  .get().length;
const galleryImageCount = extract(richContent.nodes)
  .map(({ galleryData }) => galleryData?.items || [])
  .map(
    items =>
      items.filter(({ metadata }) => (!!metadata && metadata?.type === 'image') || !metadata?.type)
        .length
  )
  .get()
  .reduce((sum, count) => sum + count, 0);
```

4. Extract list item texts, grouped by lists:

```ts
const listTexts = extract(richContent.nodes)
  .filter(({ type }) => type === Node_Type.BULLETED_LIST || type === Node_Type.ORDERED_LIST)
  .chain(n => extract(n).map(({ textData }) => textData?.text || ''))
  .map(extractor => extractor.get());
```

Note the recursive `extract` and `chain` combination.

### Content Modifier

#### modify API

The Content Modifier provides access to content nodes regardless of their nesting level.

The `modify` API accepts RichContent object, and exposes `Modifier`:

```ts
interface Modifier {
  filter: (pred: (node: Node) => boolean) => Modifier;
  set: (setter: (node: Node) => Node | Node[]) => RichContent;
}
```

#### Examples

1. Modify image nodes - ensure altText is not empty:

```ts
import { modify } from '@wix/ricos';
import { content } from 'your-own-content-from-somewhere';
...
const newContent = modify(content)
  .filter(n => n.type === Node_Type.IMAGE)
  .set(n => ({
    ...n,
    imageData: {
      ...n.imageData,
      altText: n.imageData?.altText || 'Custom alt text',
    },
  }));
```

2. Multiply dividers:

```ts
const newContent = modify(content)
  .filter(({ type }) => type === Node_Type.DIVIDER)
  .set(n => [n, n]);
```

### Content Builder

This doc focuses on various API types, and not the exhaustive list of APIs. Examples based on Image demonstrate the "block" plugin related APIs like Image, Divider, etc -- all the plugins that require a separate block (unlike "inline" plugins).
Examples based on Paragraph demonstrate text related APIs.

The Content Builder is stateless and immutable. This means, each API accepts RichContent to process, and returns a new copy of RichContent.

All the building blocks (e.g. ImageData objects) are defined in `ricos-schema` package.

#### Instantiation

The ContentBuilder depends on key generation utility `() => string` which provides unique node keys. The `wix-rich-content-common` package exports such utility.

```ts
import {
  generateKey,
  setupContentBuilder,
} from '@wix/ricos';

const api = setupContentBuilder(generateKey);
```

#### Append / insert new content

The _add_ methods add new content nodes. The node position is defined either by index (i.e. sequential number inside nodes collection), or by before/after key (adds the content relatively to a given existing node addressed by key). If no index/before/after keys provided, the new content appended to the end.

```ts
addImage({
  data: ImageData,
  index?: number,
  before?: string,
  after?: string,
  content: RichContent
}): RichContent;

addParagraph({
  text?: string | TextData | (string | TextData)[],
  data?: ParagraphData,
  index?: number,
  before?: string,
  after?: string
}): RichContent;

```

##### Adding bullet/ordered list

The list addition API is a bit different. It accepts _items_ parameter which contains textual data, and an optional _data_ parameter.

This parameter determines the default data for paragraph that contains text. At the moment, lists support only paragraphs as list item containers.

```ts
addBulletList({
  items: string | TextData | ListItemData | (string | TextData | ListItemData)[],
  data?: ParagraphData,
  index?: number,
  before?: string,
  after?: string
}): RichContent;

```

#### Examples

##### Image

```ts
const api = setupContentBuilder(generateKey);
const imageData: ImageData = {
  containerData: {
    width: { size: PluginContainerData_Width_Type.SMALL },
    alignment: PluginContainerData_Alignment.CENTER,
  },
  image: {...},
};
const content = api.addImage({ data: imageData, content: { nodes: [] } });
```

returns the following content:

```ts
{
  nodes: [
    {
      type: Node_Type.IMAGE,
      imageData,
      nodes: [],
      key: 'foo',
    },
  ],
}
```

##### List

```ts
const paragraphData: ParagraphData = {
  textStyle: {
    textAlignment: TextStyle_TextAlignment.RIGHT,
  },
};

const content = api.addBulletList({
  items: ['item1', 'item2'],
  data: paragraphData,
  content: { nodes: [] },
});
```

results in:

```ts
{
  nodes: [
    {
      type: Node_Type.BULLETED_LIST,
      key: 'foo',
      nodes: [
        {
          type: Node_Type.LIST_ITEM,
          key: 'foo',
          nodes: [
            {
              type: Node_Type.PARAGRAPH,
              key: 'foo',
              paragraphData: {
                textStyle: {
                  textAlignment: TextStyle_TextAlignment.RIGHT,
                },
              },
              nodes: [
                {
                  key: 'foo',
                  type: Node_Type.TEXT,
                  textData: {
                    text: 'item1',
                    decorations: [],
                  },
                  nodes: [],
                },
              ],
            },
          ],
        },
        {
          type: Node_Type.LIST_ITEM,
          key: 'foo',
          nodes: [
            {
              type: Node_Type.PARAGRAPH,
              key: 'foo',
              paragraphData: {
                textStyle: {
                  textAlignment: TextStyle_TextAlignment.RIGHT,
                },
              },
              nodes: [
                {
                  key: 'foo',
                  type: Node_Type.TEXT,
                  textData: {
                    text: 'item2',
                    decorations: [],
                  },
                  nodes: [],
                },
              ],
            },
          ],
        },
      ],
    },
  ],
}
```

### Content Converters

#### Draft.js

Ricos is in the preparation phase of migrating from a [draft-js](https://draftjs.org) based content to our new [Ricos](https://github.com/wix/ricos/tree/master/packages/wix-ricos-schema) schema. To support this we provide conversion functions to & from draft.

```tsx
fromDraft: (draftJSON: DraftContent, opts: FromDraftOptions = {}) => RichContent;
toDraft: (content: RichContent) => DraftContent;
ensureRicosContent: (content: RichContent | DraftContent, opts?: FromDraftOptions = {}) =>
  RichContent;
ensureDraftContent: (content: RichContent | DraftContent) => DraftContent;
```

##### FromDraftOptions

By default, the `fromDraft` API throws error when it encounters invalid `DraftContent`. Sometimes, this behavior is not desired.

The `opts: FromDraftOptions` parameter allows to alter it, by setting `{ ignoreUnsupportedValues: true }`. In this case, any invalid values are gracefully ignored.

##### Example

```tsx
import {
  type DraftContent,
  fromDraft,
  toDraft,
  RichContent,
  RicosEditor,
} from '@wix/ricos';
import ricosContent from './ricosContent.json';

const draftContent: DraftContent = toDraft(ricosContent);

const onChange = (content: DraftContent) => publishContent(fromDraft(content));

<RicosEditor content={draftContent} onChange={onChange} />;
```

#### Plain Text

Ricos is a rich text & rich content editor. The following functions allow converting between plain text and rich content.

##### Plain text to rich content

```tsx
fromPlainText: (text: string) => RichContent;
```

##### Example

```tsx
import {
  type DraftContent,
  fromPlainText,
  toDraft,
  RichContent,
  RicosEditor,
} from '@wix/ricos';

const TEXT = 'Hello there!';

const content: RichContent = fromPlainText(TEXT);
const draftContent: DraftContent = toDraft(content);

<RicosEditor content={draftContent} />;
```

##### Rich content to plain text

```tsx
toPlainText: (
  content: RichContent,
  options?: {
    urlShortener?: (url: string) => Promise<string>;
    getVideoUrl?: (fileId: string) => Promise<string>;
  }
) => Promise<string>;
```

###### urlShortener

This function will be called on image source URLs and can be used with URL shortener services.

###### getVideoUrl

Used to resolve a `VideoSource` object into an absolute URL.<br>
Defaults to `src => https://video.wixstatic.com/${src.pathname}`

##### Example

```tsx
import { toPlainText } from '@wix/ricos';
import ricosContent from './ricosContent.json';

const text: string = await toPlainText(ricosContent);

console.log(text);
```

#### HTML

The following functions allow converting rich content to and from HTML.

Supporting:

- Headings
- Bold
- Italic
- Links
- Lists
- Line break
- Emojis

##### HTML to rich content

###### Overview

The `fromRichTextHtml` function is used to convert HTML to a defined schema format. It processes the HTML data, applies a set of conversion rules, and provides an output in the `RichContent` format.
Interceptor function allows users to modify the conversion perocess as per their requirements.

###### API

```tsx
fromRichTextHtml: (
  html: string,
  interceptorFn: (context: { contentNode: ContentNode; context: Context; result: Node[] }) => Node[]
) => RichContent;
```

| interceptorFn.context | description                                                                                                                                                                                                                  |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| contentNode           | The contentNode parameter refers to the currently processed node in the HTML document. It contains the information of the node in the HTML that is being converted to the schema format.                                     |
| context               | The context parameter offers detailed information about the HTML document and the capability to traverse the HTML fragments. It gives access to the HTML being processed, allowing for more complex or global modifications. |
| result                | The result parameter represents the outcome of the conversion if no interception was to occur.                                                                                                                               |

##### Examples

###### 1. Basic example

```tsx
import {
  fromRichTextHtml,
  RicosEditor,
} from '@wix/ricos';

const html = '<p><strong>Bold</strong> <em>italic</em> <u>underline</u></p>';

const content = fromRichTextHtml(html);

<RicosEditor content={content} />;
```

###### 2. Traversing unrecognized HTML fragments

When encountering unrecognized or custom HTML tags for which we don't have default rules, the default behavior
is to stop traversing such HTML nodes. However, the interceptor can leverage the context to traverse and process
the content within these tags.

```ts
import {
  fromRichTextHtml,
  RichContent,
} from '@wix/ricos';

const html = '<div><p>some text</p></div>';

const content = fromRichTextHtml(html, ({ contentNode, result, context }) => {
  if (contentNode.nodeName === 'div') {
    return context.visit(contentNode);
  }

  return result;
});
```

###### 3. Overriding HTML tag

The interceptor can convert unrecognized HTML tags into recognizable ones, such as converting an unrecognized tag into a paragraph node.

```ts
import {
  createParagraphNode,
  fromRichTextHtml,
  RichContent,
} from '@wix/ricos';

const html = '<unrecognized>text</unrecognized>';

const content = fromRichTextHtml(html, ({ contentNode, result, context }) => {
  if (contentNode.nodeName === 'unrecognized') {
    return [createParagraphNode(context.visit(contentNode))];
  }

  return result;
});
```

##### Rich content to HTML

###### Overview

The `toHtml` function is a converter used to transform data from a defined schema
format back to HTML. It processes the `RichContent` data and translates it into HTML
according to a predefined set of conversion rules.

###### API

```tsx
toHtml: (
  content: RichContent,
  interceptorFn: (context: { node: Node; result: string | string[] }) => string | string[]
) => string;
```

| interceptorFn.context | description                                                                                                                                        |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| node                  | The node parameter is the current node being processed in the schema data. It provides information about the node that is being converted to HTML. |
| result                | The result parameter represents the outcome of the conversion if no interception was to occur.                                                     |

##### Examples

###### 1. Basic example

```tsx
import {
  RichContent,
  toHtml,
} from '@wix/ricos';
import ricosContent from './ricosContent.json';

const html = toHtml(RichContent.fromJSON(ricosContent));
```

###### 2. Capturing and modifying the conversion result

The interceptor can be used to modify the HTML representation of specific node types. For instance,
it can replace the default `<p>` and `<text>` tags with `<p-override>` and text-override respectively.

```ts
import {
  type InterceptorToHtml,
  toHtml,
} from '@wix/ricos';

const paragraphContent: RichContent = {
  // content which has paragraph with text value "some-text"
  ...
};

const inerceptor: InterceptorToHtml = ({ node, result }) => {
  if (node.type === Node_Type.PARAGRAPH) {
    return `<p-override>${toHtml(node.nodes, interceptor)}</p-override>`;
  }

  if (node.type === Node_Type.TEXT) {
    return `text-override`;
  }

  return result;
}

const html = toHtml(paragraphContent, interceptor);

console.log(html); // <p-override>text-override</p-override>
```

### Extract Rich Content Media

#### Extracted Data

The extracted data will be an array of objects, each object representing a media item. There are two types of media items that can be extracted:

##### ImageMedia

```ts
import { type ImageMedia } from '@wix/ricos';
```

| property  | optional | info                                                            |
| --------- | -------- | --------------------------------------------------------------- |
| `type`    | No       | The type of media item. For images, this will always be `image` |
| `url`     | Yes      | The URL of the image                                            |
| `alt`     | Yes      | The alt text of the image                                       |
| `caption` | Yes      | The title of the image                                          |
| `width`   | Yes      | The width of the image                                          |
| `height`  | Yes      | The height of the image                                         |

##### VideoMedia

```ts
import { type VideoMedia } from '@wix/ricos';
```

| property       | optional | info                                                            |
| -------------- | -------- | --------------------------------------------------------------- |
| `type`         | No       | The type of media item. For videos, this will always be `video` |
| `url`          | Yes      | The URL of the video                                            |
| `thumbnailUrl` | Yes      | The URL of the video thumbnail                                  |
| `uploadDate`   | Yes      | The date the video was uploaded                                 |
| `width`        | Yes      | The width of the video                                          |
| `height`       | Yes      | The height of the video                                         |
| `title`        | Yes      | The title of the video                                          |
| `duration`     | Yes      | The duration of the video in seconds                            |

###### Example

```ts
[
  {
    type: 'image',
    url: 'https://example.com/image.jpg',
    alt: 'An image',
    caption: 'A beautiful image',
    width: 1920,
    height: 1080,
  },
  {
    type: 'video',
    url: 'https://example.com/video.mp4',
    thumbnailUrl: 'https://example.com/video-thumbnail.jpg',
    uploadDate: '2021-01-01T00:00:00Z',
    width: 1920,
    height: 1080,
    title: 'A video',
    duration: 60,
  },
];
```

#### getMediaData

##### Parameters

| name                | type                | required | description                                                                    |
| ------------------- | ------------------- | -------- | ------------------------------------------------------------------------------ |
| `content`           | `RichContent`       | Yes      | The rich content from which to extract media data                              |
| `mediaUrlResolvers` | `MediaUrlResolvers` | No       | An object with functions to resolve media IDs to URLs. See below for more info |

```ts
type MediaUrlResolvers = {
  image?: (id: string) => string;
  video?: (id: string) => string;
  thumbnail?: (id: string) => string;
};

type GetMediaData = (
  content: RichContent,
  mediaUrlResolvers?: MediaUrlResolvers
) => (ImageMedia | VideoMedia)[];
```

##### Code example

```ts
import { getMediaData } from '@wix/ricos';

const content = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'aho6i53',
      nodes: [],
      paragraphData: {},
    },
    {
      type: 'IMAGE',
      id: 'w1b3852',
      nodes: [],
      imageData: {
        containerData: {
          width: {
            size: 'CONTENT',
          },
          alignment: 'CENTER',
          textWrap: true,
        },
        image: {
          src: {
            id: '8bb438_94a4e8c055d84430af593cb3d31fd67a.jpg',
          },
          width: 3370,
          height: 2247,
        },
        altText: 'Text',
        caption: 'Capt',
      },
    },
    {
      type: 'PARAGRAPH',
      id: '2ocq654',
      nodes: [],
      paragraphData: {},
    },
  ],
};

const mediaData = getMediaData(content);

console.log(mediaData);

// [
//   {
//     type: 'image',
//     url: 'https://static.wixstatic.com/media/8bb438_94a4e8c055d84430af593cb3d31fd67a.jpg',
//     alt: 'Text',
//     caption: 'Capt',
//     width: 3370,
//     height: 2247,
//   },
// ];
```

### Metadata Utilities

##### `ensureContentId`

Ensure that a given `RichContent` object contains an id by generating one (if not exist) or leaving it as is (if it does).

If no content is provided, an empty content with a new id is generated.

###### Example

```ts
import { ensureContentId } from '@wix/ricos';
import { someContent } from 'somwhere'; // content without ID:

const content1: RichContent = ensureContentId();
// => Empty content with ID

const content2: RichContent = ensureContentId(someContent);
// = `someContent` with newly generated ID

const content3: RichContent = ensureContentId(content2);
// = `content2` (identical)
```

##### `createEmptyContent`

Creates an empty, valid `RichContent` object

###### Example

```ts
import { createEmptyContent } from '@wix/ricos';

const content: RichContent = createEmptyContent();
```

### Translatables API

`Translation API` provides methods for translatable fragment extraction and replacement within a `RichContent` instance.

`toTanslatables: (content: RichContent) => Translatable[]`

`translateContent: (content: RichContent, translatables: Translatable[]) => RichContent`

where `Translatable` consists of `text` and some metadata fields required for translation fragment replacement.

For the purpose of translating content, there are 3 stages that you should follow:

1. **Extraction**: getting a `Translatable[]` from a `RichContent` object
2. **Translation**: modifying the `Translatable[]` with translated values
3. **Application**: applying the modified `Translatable[]` array into a `RichContent` object

Ricos does not provide a Translation service. Hence, stage 2 is up to your implementation

##### Example

```ts
import { translateToSpanish } from '3rd-party-translation-service';
import { toTranslatables, translateContent } from '@wix/ricos';

function translate(translatables: Translatable[]) {
  return translatables.map(t => ({ ...t, text: translateToSpanish(t.text) }));
}

const english: RichContent = { ... };

const englishChunks: Translatable[] = toTranslatables(english);

const spanishChunks = translate(englishChunks);

const spanish: RichContent = translateContent(english, spanishChunks);
```

### Truncate Content

```tsx
truncateContent: (
  content: RichContent | DraftContent,
  opts?: { wordsCount?: number; maxPlugins?: number; blocksCount?: number }
) => {
  content: RichContent | DraftContent;
  isTruncated: boolean;
};
```

Truncate existing content according to number of blocks, and optional words count and/or plugin count.

The content will be truncated by the first limit reached.
For example passing `blocksCount=5`, `wordsCount=100`, `maxPlugins=3` will yield a content that is not more than 5 blocks, shorter than 100 words and have no more than 3 plugin blocks

#### Example Usage

```js
import { truncateContent } from '@wix/ricos';
const {content} = truncateContent(fullContent, 4)
<RicosViewer content={content} ... />
```

### MediaUrlResolvers

The `mediaUrlResolvers` parameter is an object with three optional functions:

- `image` - A function that resolves an image ID to a URL
- `video` - A function that resolves a video ID to a URL
- `thumbnail` - A function that resolves a thumbnail ID to a URL

These functions are used to resolve the IDs of media items to URLs. If a function is not provided, the default ID resolver will be used.

#### Example

```ts
const mediaUrlResolvers = {
  image: id => `https://example.com/images/${id}`,
  video: id => `https://example.com/videos/${id}`,
  thumbnail: id => `https://example.com/thumbnails/${id}`,
};

const mediaData = getMediaData(content, mediaUrlResolvers);

console.log(mediaData);

// [
//   {
//     type: 'image',
//     url: 'https://example.com/images/8bb438_94a4e8c055d84430af593cb3d31fd67a.jpg',
//     alt: 'Text',
//     caption: 'Capt',
//     width: 3370,
//     height: 2247,
//   },
// ];
```

## Media handling

Recommended plugin configuration for uploading via browser file selector is

```
{
    helpers: {
	  handleFileUpload: imageUploadHandler
	},
    config: {
      [VIDEO_TYPE]: {
        handleFileUpload: videoUploadHandler,
      },
      [AUDIO_TYPE]: {
        handleFileUpload: audioUploadHandler,
      },
      [FILE_UPLOAD_TYPE]: {
        onFileSelected: fileUploadHandler,
      },
    },
  };
```

And the recommended config for uploading via custom file selector is

```
  {
    helpers: {
	  handleFileSelection: imageSelectionHandler
	},
    config: {
      [VIDEO_TYPE]: {
        handleFilSelection: videoSelectionHandler,
      },
      [AUDIO_TYPE]: {
        handleFilSelection: audioSelectionHandler,
      },
      [FILE_UPLOAD_TYPE]: {
        handleFileSelection: fileSelectionHandler,
      },
    },
  };
```

Image and gallery plugins are both handled with a single function.
Supplying a handleFileSelection function will override the plugin's native file selector handler (e.g. for images - handleFileSelection overrides onFilesChange).

Please refer to Editor.js and EditorPlugins.js for examples.

## Adsense Support

In order to resolve this issue with adsense support - https://support.wix.com/en/article/adding-google-adsense-ads-to-blog-posts-in-the-new-wix-blog

An additional prop is needed both for Rich Content Editor & Rich Content Viewer

| Prop Name    | Type              | Description                                                                          |
| ------------ | ----------------- | ------------------------------------------------------------------------------------ |
| `siteDomain` | String (Full URL) | User's site URL - when present automatically adds the needed line to support adsense |

## Related Types

Base types inherited by the props above.

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">FocusPosition</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> FocusPosition = 'start' | 'end' | 'all' | number</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">BISettings</span>

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> BISettings</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>biCallbacks</strong><span class="text-slate-400">?</span>: BICallbacks</code></dt>
<dd><p>Optional Callback functions that you can hook</p></dd>
<dt><code><strong>consumer</strong>: 'Blog' | 'Forum' | 'Challenges' | string</code></dt>
<dd><p>Blog, Forum, or any other string...</p></dd>
<dt><code><strong>containerId</strong>: string</code></dt>
<dd><p>appDefId</p></dd>
<dt><code><strong>contentId</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>ID of Post / Comment / Document / anything equivalent</p></dd>
<dt><code><strong>defaultParams</strong><span class="text-slate-400">?</span>: DefaultBIParams</code></dt>
<dd><p>Essential BI Fields for Ricos, inherited from platform. Check out [extractUoUParams()](https://bo.wix.com/pages/wix-ricos/docs/api#extractuouparams-util) to easily extract fields from yoshi platform variables.</p></dd>
<dt><code><strong>logger</strong><span class="text-slate-400">?</span>: Logger</code></dt>
<dd><p>Logger instance, required by consumers via Yoshi Editor Flow</p></dd>
<dt><code><strong>platform</strong>: 'Editor' | 'Livesite' | 'BM' | 'OneApp'</code></dt>
<dd><p>Editor / Livesite / BM / OneApp</p></dd>
<dt><code><strong>postId</strong>: string</code></dt>
<dd></dd>
<dt><code><strong>usage</strong><span class="text-slate-400">?</span>: 'Post' | 'Feed' | 'Comment' | string</code></dt>
<dd><p>Post / Feed / Comment / ...</p></dd>
<dt style="opacity: 0.5;"><code><strong>webBiLogger</strong><span class="text-slate-400">?</span>: WebBiLogger</code></dt>
<dd><p><em>Deprecated.</em> If your WebBiLogger is handy, please provide it to improve runtime performance. Otherwise, Ricos will dynamically load it.</p></dd>
</dl>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">ColorPaletteManager</span>

Unified color manager for sharing custom colors and gradients across all plugins.

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> ColorPaletteManager = {     colors: string[];     setColors: <a href="#setcolors"><span class="font-mono">SetColors</span></a>;     gradients?: Gradient[];     setGradients?: SetGradients; }</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">DebugMode</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> DebugMode = 'all' | 'shortcuts' | 'mobile' | 'prosemirror' | 'events' | 'extensions'</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">Environment</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> Environment = 'businessManager' | 'liveSite' | 'wixel'</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">GeneralLinkSettings</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> GeneralLinkSettings = {     enabledLinkTypes?: PublicLinkType[];     initialLinkOptions?: LinkOptions;     autoUnderline?: boolean; }</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">HistorySettings</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> HistorySettings = {     onHistoryChange?: () =&gt; void;     keyboardShortcuts?: HistoryKeyboardShortcuts | false; }</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosDocument</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> RicosDocument = {     nodes: <a href="#ricosdocumentnode"><span class="font-mono">RicosDocumentNode</span></a>[];     metadata?: Metadata;     documentStyle?: DocumentStyle; }</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">LinkSettings</span>

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> LinkSettings</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>anchorTarget</strong><span class="text-slate-400">?</span>: AnchorTarget</code></dt>
<dd></dd>
<dt><code><strong>customAnchorScroll</strong><span class="text-slate-400">?</span>: CustomAnchorScroll</code></dt>
<dd></dd>
<dt><code><strong>rel</strong><span class="text-slate-400">?</span>: <a href="#link_rel"><span class="font-mono">Link_Rel</span></a></code></dt>
<dd></dd>
<dt><code><strong>relValue</strong><span class="text-slate-400">?</span>: RelValue</code></dt>
<dd></dd>
</dl>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">onAtomicBlockFocus</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> onAtomicBlockFocus = (params: {     blockKey?: string;     type?: string;     data?: RicosEntity['data']; }) =&gt; void</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">OnContentChangeFunction</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> OnContentChangeFunction = (content: <a href="#ricosdocument"><span class="font-mono">RicosDocument</span></a>) =&gt; void</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">OnSelectionChangeFunction</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> OnSelectionChangeFunction = (content: <a href="#ricosdocument"><span class="font-mono">RicosDocument</span></a>) =&gt; void</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">EditorPluginOptions</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> EditorPluginOptions = AiPluginEditorConfig | InlineAiPluginConfig | LayoutPluginOptions | LayoutUppercasePluginOptions | FontSizePluginOptions | <a href="/api-docs/plugins/emoji#emojiplugineditorconfig"><span class="font-mono">EmojiPluginEditorConfig</span></a> | <a href="/api-docs/plugins/headings#headingsplugineditorconfig"><span class="font-mono">HeadingsPluginEditorConfig</span></a> | IndentPluginEditorConfig | LineSpacingPluginEditorConfig | ActionButtonPluginEditorConfig | <a href="/api-docs/plugins/audio#audioplugineditorconfig"><span class="font-mono">AudioPluginEditorConfig</span></a> | <a href="#codeblockplugineditorconfig"><span class="font-mono">CodeBlockPluginEditorConfig</span></a> | CollapsibleListPluginEditorConfig | <a href="#dividerplugineditorconfig"><span class="font-mono">DividerPluginEditorConfig</span></a> | <a href="#fileplugineditorconfig"><span class="font-mono">FilePluginEditorConfig</span></a> | <a href="#galleryplugineditorconfig"><span class="font-mono">GalleryPluginEditorConfig</span></a> | <a href="/api-docs/plugins/giphy#giphyplugineditorconfig"><span class="font-mono">GiphyPluginEditorConfig</span></a> | <a href="/api-docs/plugins/hashtag#hashtagplugineditorconfig"><span class="font-mono">HashtagPluginEditorConfig</span></a> | HtmlPluginEditorConfig | TocPluginEditorConfig | ImagePluginEditorConfig | LinkPluginEditorConfig | <a href="#linkbuttonplugineditorconfig"><span class="font-mono">LinkButtonPluginEditorConfig</span></a> | <a href="#linkpreviewplugineditorconfig"><span class="font-mono">LinkPreviewPluginEditorConfig</span></a> | <a href="#mentionsplugineditorconfig"><span class="font-mono">MentionsPluginEditorConfig</span></a> | <a href="#pollplugineditorconfig"><span class="font-mono">PollPluginEditorConfig</span></a> | <a href="#spoilerplugineditorconfig"><span class="font-mono">SpoilerPluginEditorConfig</span></a> | <a href="#tableplugineditorconfig"><span class="font-mono">TablePluginEditorConfig</span></a> | TextColorPluginEditorConfig | TextHighlightPluginEditorConfig | VerticalEmbedPluginEditorConfig | <a href="#videoplugineditorconfig"><span class="font-mono">VideoPluginEditorConfig</span></a> | FontFamilyPluginEditorConfig | ShapePluginOptions | CardPluginEditorConfig</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">TextAlignment</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> TextAlignment = 'left' | 'center' | 'right' | 'justify'</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosDocumentNode</span>

Union of all Ricos document node types.

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> RicosDocumentNode = <a href="#blockquotenode"><span class="font-mono">BlockquoteNode</span></a> | <a href="#captionnode"><span class="font-mono">CaptionNode</span></a> | CardNode | <a href="#codeblocknode"><span class="font-mono">CodeBlockNode</span></a> | <a href="#headingnode"><span class="font-mono">HeadingNode</span></a> | LayoutNode | LayoutCellNode | <a href="#orderedlistnode"><span class="font-mono">OrderedListNode</span></a> | <a href="#bulletedlistnode"><span class="font-mono">BulletedListNode</span></a> | CheckboxListNode | <a href="#listitemnode"><span class="font-mono">ListItemNode</span></a> | <a href="#paragraphnode"><span class="font-mono">ParagraphNode</span></a> | <a href="#textnode"><span class="font-mono">TextNode</span></a> | <a href="#appembednode"><span class="font-mono">AppEmbedNode</span></a> | AudioNode | <a href="#buttonnode"><span class="font-mono">ButtonNode</span></a> | <a href="#collapsiblelistnode"><span class="font-mono">CollapsibleListNode</span></a> | <a href="#collapsibleitemnode"><span class="font-mono">CollapsibleItemNode</span></a> | <a href="#collapsibleitemtitlenode"><span class="font-mono">CollapsibleItemTitleNode</span></a> | <a href="#collapsibleitembodynode"><span class="font-mono">CollapsibleItemBodyNode</span></a> | <a href="#dividernode"><span class="font-mono">DividerNode</span></a> | <a href="#embednode"><span class="font-mono">EmbedNode</span></a> | ExternalNode | <a href="#filenode"><span class="font-mono">FileNode</span></a> | <a href="#gallerynode"><span class="font-mono">GalleryNode</span></a> | <a href="#gifnode"><span class="font-mono">GifNode</span></a> | <a href="#htmlnode"><span class="font-mono">HtmlNode</span></a> | <a href="#imagenode"><span class="font-mono">ImageNode</span></a> | <a href="#linkpreviewnode"><span class="font-mono">LinkPreviewNode</span></a> | <a href="#pollnode"><span class="font-mono">PollNode</span></a> | <a href="#shapenode"><span class="font-mono">ShapeNode</span></a> | SmartBlockNode | SmartBlockCellNode | <a href="#tablenode"><span class="font-mono">TableNode</span></a> | <a href="#tablerownode"><span class="font-mono">TableRowNode</span></a> | <a href="#tablecellnode"><span class="font-mono">TableCellNode</span></a> | TocNode | <a href="#videonode"><span class="font-mono">VideoNode</span></a></code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosPalette</span>

Colors scheme for `Ricos`.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosPalette</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>accent1</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd></dd>
<dt><code><strong>accent2</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd></dd>
<dt><code><strong>accent3</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd></dd>
<dt><code><strong>accent4</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd></dd>
<dt><code><strong>bgColor</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd></dd>
<dt><code><strong>format</strong>: 'color' | 'rgbTuple'</code></dt>
<dd><p>Declares how color values in this palette should be interpreted. - `'color'`: values are standard CSS color strings (hex, rgb(), named colors, etc.) - `'rgbTuple'`: values are bare RGB tuples (e.g., `'255, 0, 0'`)</p></dd>
<dt><code><strong>shade1</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd></dd>
<dt><code><strong>shade2</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd></dd>
<dt><code><strong>shade3</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd></dd>
<dt><code><strong>textColor</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd></dd>
</dl>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosCustomStyles</span>

Per-element style overrides. Unlike `colorPalette` (which paints colors broadly across every plugin), `customStyles` lets you target a single element — a heading, the quote block, the footer toolbar, the editor container — with arbitrary CSS. When both apply, `customStyles` wins on conflict.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosCustomStyles</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>audio</strong><span class="text-slate-400">?</span>: CustomAudioStyle</code></dt>
<dd><p>Style overrides for the Audio plugin's card.</p></dd>
<dt><code><strong>button</strong><span class="text-slate-400">?</span>: CustomButtonStyle</code></dt>
<dd><p>Style overrides for the Button plugin's primary button shape.</p></dd>
<dt><code><strong>card</strong><span class="text-slate-400">?</span>: CustomCardStyle</code></dt>
<dd><p>Style overrides for the per-plugin selection card chrome (selection border, background image).</p></dd>
<dt><code><strong>codeBlock</strong><span class="text-slate-400">?</span>: CustomCodeBlockStyle</code></dt>
<dd><p>Style overrides for the Code Block plugin.</p></dd>
<dt><code><strong>container</strong><span class="text-slate-400">?</span>: CustomContainerStyles</code></dt>
<dd><p>Style overrides for the outermost content container — top/bottom padding.</p></dd>
<dt><code><strong>divider</strong><span class="text-slate-400">?</span>: CustomDividerStyle</code></dt>
<dd><p>Style overrides for the Divider plugin.</p></dd>
<dt><code><strong>editor</strong><span class="text-slate-400">?</span>: CustomEditorStyle</code></dt>
<dd><p>Style overrides for the editable container: `minHeight`/`maxHeight`, placeholder color, +button position.</p></dd>
<dt><code><strong>file</strong><span class="text-slate-400">?</span>: CustomFileStyle</code></dt>
<dd><p>Style overrides for the File Upload plugin's card.</p></dd>
<dt><code><strong>footerToolbar</strong><span class="text-slate-400">?</span>: CustomFooterToolbarStyle</code></dt>
<dd><p>Style overrides for the inline plus-button bar rendered at the bottom of the editor.</p></dd>
<dt><code><strong>h1</strong><span class="text-slate-400">?</span>: <a href="#customtextualstyle"><span class="font-mono">CustomTextualStyle</span></a></code></dt>
<dd><p>Style overrides applied to `&lt;h1&gt;` elements rendered by Ricos.</p></dd>
<dt><code><strong>h2</strong><span class="text-slate-400">?</span>: <a href="#customtextualstyle"><span class="font-mono">CustomTextualStyle</span></a></code></dt>
<dd><p>Style overrides applied to `&lt;h2&gt;` elements rendered by Ricos.</p></dd>
<dt><code><strong>h3</strong><span class="text-slate-400">?</span>: <a href="#customtextualstyle"><span class="font-mono">CustomTextualStyle</span></a></code></dt>
<dd><p>Style overrides applied to `&lt;h3&gt;` elements rendered by Ricos.</p></dd>
<dt><code><strong>h4</strong><span class="text-slate-400">?</span>: <a href="#customtextualstyle"><span class="font-mono">CustomTextualStyle</span></a></code></dt>
<dd><p>Style overrides applied to `&lt;h4&gt;` elements rendered by Ricos.</p></dd>
<dt><code><strong>h5</strong><span class="text-slate-400">?</span>: <a href="#customtextualstyle"><span class="font-mono">CustomTextualStyle</span></a></code></dt>
<dd><p>Style overrides applied to `&lt;h5&gt;` elements rendered by Ricos.</p></dd>
<dt><code><strong>h6</strong><span class="text-slate-400">?</span>: <a href="#customtextualstyle"><span class="font-mono">CustomTextualStyle</span></a></code></dt>
<dd><p>Style overrides applied to `&lt;h6&gt;` elements rendered by Ricos.</p></dd>
<dt><code><strong>hashtag</strong><span class="text-slate-400">?</span>: <a href="#customtextualstyle"><span class="font-mono">CustomTextualStyle</span></a></code></dt>
<dd><p>Style overrides for hashtag decorations.</p></dd>
<dt><code><strong>image</strong><span class="text-slate-400">?</span>: CustomImageStyle</code></dt>
<dd><p>Style overrides applied to the Image plugin's frame.</p></dd>
<dt><code><strong>layout</strong><span class="text-slate-400">?</span>: CustomLayoutStyle</code></dt>
<dd><p>Style overrides for the editor's overall layout container (background, backdrop).</p></dd>
<dt><code><strong>link</strong><span class="text-slate-400">?</span>: <a href="#customtextualstyle"><span class="font-mono">CustomTextualStyle</span></a></code></dt>
<dd><p>Style overrides for inline link decorations.</p></dd>
<dt><code><strong>linkPreview</strong><span class="text-slate-400">?</span>: CustomLinkPreviewStyle</code></dt>
<dd><p>Style overrides for the Link Preview plugin's card.</p></dd>
<dt><code><strong>mention</strong><span class="text-slate-400">?</span>: CustomMentionStyle</code></dt>
<dd><p>Style overrides for inline mention decorations.</p></dd>
<dt><code><strong>p</strong><span class="text-slate-400">?</span>: <a href="#customtextualstyle"><span class="font-mono">CustomTextualStyle</span></a></code></dt>
<dd><p>Style overrides applied to paragraph (`&lt;p&gt;`) elements.</p></dd>
<dt><code><strong>quote</strong><span class="text-slate-400">?</span>: CustomQuoteStyle</code></dt>
<dd><p>Style overrides for the blockquote element — includes the left-border indent.</p></dd>
<dt><code><strong>secondaryButton</strong><span class="text-slate-400">?</span>: CustomSecondaryButtonStyle</code></dt>
<dd><p>Style overrides for the Button plugin's secondary variant.</p></dd>
<dt><code><strong>smartBlock</strong><span class="text-slate-400">?</span>: CustomSmartBlockStyle</code></dt>
<dd><p>Style overrides for the Smart Block plugin's accent, background, and track colors.</p></dd>
<dt><code><strong>staticToolbar</strong><span class="text-slate-400">?</span>: CustomStaticToolbarStyles</code></dt>
<dd><p>Style overrides for the static (anchored) text toolbar — currently the bottom border only.</p></dd>
<dt><code><strong>table</strong><span class="text-slate-400">?</span>: CustomTableStyle</code></dt>
<dd><p>Style overrides for the Table plugin (borders, cell background).</p></dd>
<dt><code><strong>toc</strong><span class="text-slate-400">?</span>: CustomTocStyle</code></dt>
<dd><p>Style overrides for the Table of Contents plugin.</p></dd>
<dt><code><strong>verticalEmbed</strong><span class="text-slate-400">?</span>: CustomVerticalEmbedStyle</code></dt>
<dd><p>Style overrides for the Vertical Embed plugin.</p></dd>
</dl>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosNativeStyles</span>

React-Native-flavoured style overrides for native (mobile) Ricos builds. Only consumed when `RicosTheme.useNativeStyles` is `true` — ignored by the web renderer.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosNativeStyles</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>bgColor</strong><span class="text-slate-400">?</span>: {         backgroundColor?: CSSProperties['backgroundColor'];     }</code></dt>
<dd><p>Native background color of the editor surface.</p></dd>
<dt><code><strong>coverImage</strong><span class="text-slate-400">?</span>: {         width?: CSSProperties['width'];         height?: CSSProperties['height'];         border?: CSSProperties['border'];         margin?: CSSProperties['margin'];     }</code></dt>
<dd><p>Size and framing of plugin cover images.</p></dd>
<dt><code><strong>primaryText</strong><span class="text-slate-400">?</span>: {         fontWeight?: CSSProperties['fontWeight'];     }</code></dt>
<dd><p>Weight applied to primary text (headings, emphasized labels).</p></dd>
<dt><code><strong>secondary</strong><span class="text-slate-400">?</span>: {         backgroundColor?: CSSProperties['backgroundColor'];     }</code></dt>
<dd><p>Background color for secondary surfaces (cards, panels).</p></dd>
<dt><code><strong>text</strong><span class="text-slate-400">?</span>: {         fontFamily?: CSSProperties['fontFamily'];         color?: CSSProperties['color'];     }</code></dt>
<dd><p>Default text family + color.</p></dd>
<dt><code><strong>whitebox</strong><span class="text-slate-400">?</span>: {         borderRadius?: CSSProperties['borderRadius'];     }</code></dt>
<dd><p>Border radius of the floating settings sheet ("whitebox").</p></dd>
</dl>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosSettingsStyles</span>

Style overrides for the editor's floating panels and settings modals. Most fields are deprecated and produce no effect — only `modals` (z-index layering for the toolbar / popups / drawers / dialogs / tooltips / notifications) is supported going forward.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosSettingsStyles</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>bgColor</strong><span class="text-slate-400">?</span>: {         backgroundColor?: CSSProperties['backgroundColor'];     }</code></dt>
<dd></dd>
<dt><code><strong>buttons</strong><span class="text-slate-400">?</span>: {         borderRadius?: CSSProperties['borderRadius'];         textColor?: CSSProperties['color'];     }</code></dt>
<dd></dd>
<dt><code><strong>colorPaletteSelectedButton</strong><span class="text-slate-400">?</span>: {         borderRadius?: CSSProperties['borderRadius'];     }</code></dt>
<dd></dd>
<dt><code><strong>disabled</strong><span class="text-slate-400">?</span>: {         backgroundColor?: CSSProperties['backgroundColor'];     }</code></dt>
<dd></dd>
<dt><code><strong>dividers</strong><span class="text-slate-400">?</span>: {         color?: CSSProperties['color'];         height?: CSSProperties['height'];     }</code></dt>
<dd></dd>
<dt><code><strong>hover</strong><span class="text-slate-400">?</span>: {         color?: CSSProperties['color'];     }</code></dt>
<dd></dd>
<dt><code><strong>icons</strong><span class="text-slate-400">?</span>: {         color?: CSSProperties['color'];     }</code></dt>
<dd></dd>
<dt><code><strong>inputs</strong><span class="text-slate-400">?</span>: {         borderColor?: CSSProperties['borderColor'];         borderRadius?: CSSProperties['borderRadius'];         placeholderColor?: CSSProperties['color'];     }</code></dt>
<dd></dd>
<dt><code><strong>modals</strong><span class="text-slate-400">?</span>: {         toolbarZIndex?: CSSProperties['zIndex'];         popupZIndex?: CSSProperties['zIndex'];         drawerZIndex?: CSSProperties['zIndex'];         dialogZIndex?: CSSProperties['zIndex'];         tooltipZIndex?: CSSProperties['zIndex'];         notificationZIndex?: CSSProperties['zIndex'];         baseZIndex?: CSSProperties['zIndex'];     }</code></dt>
<dd></dd>
<dt><code><strong>smallButtons</strong><span class="text-slate-400">?</span>: {         borderRadius?: CSSProperties['borderRadius'];     }</code></dt>
<dd></dd>
<dt><code><strong>text</strong><span class="text-slate-400">?</span>: {         fontFamily?: CSSProperties['fontFamily'];         color?: CSSProperties['color'];     }</code></dt>
<dd></dd>
<dt><code><strong>whitebox</strong><span class="text-slate-400">?</span>: {         borderRadius?: CSSProperties['borderRadius'];         boxShadow?: CSSProperties['boxShadow'];     }</code></dt>
<dd></dd>
</dl>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosTypography</span>

Typography tokens applied across the editor and viewer. The `fontFamily` value cascades to every text node — paragraphs, headings, plugin labels — unless a `customStyles.<element>.fontFamily` overrides it for that element.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosTypography</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>fontFamily</strong><span class="text-slate-400">?</span>: CSSProperties['fontFamily']</code></dt>
<dd><p>Default font family inherited by every text element rendered by Ricos.</p></dd>
<dt><code><strong>wixTypography</strong><span class="text-slate-400">?</span>: WixTypography</code></dt>
<dd><p>WixTypography - work in progress. This field is ineffective at the moment.</p></dd>
</dl>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">AudioButton</span>

Audio source button type: `'audio'` (file upload), `'soundCloud'`, or `'spotify'`.

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> AudioButton = 'audio' | 'soundCloud' | 'spotify'</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">HeadingType</span>

Heading levels that can appear in the headings dropdown. `'P'` represents paragraph (no heading).

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> HeadingType = 'P' | 'H1' | 'H2' | 'H3' | 'H4' | 'H5' | 'H6'</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">Link_Rel</span>

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> Link_Rel = {     nofollow?: boolean;     sponsored?: boolean;     ugc?: boolean;     noreferrer?: boolean; }</code></pre>

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">CustomTextualStyle</span>

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> CustomTextualStyle</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>backgroundColor</strong><span class="text-slate-400">?</span>: CSSProperties['backgroundColor']</code></dt>
<dd></dd>
<dt><code><strong>color</strong><span class="text-slate-400">?</span>: CSSProperties['color']</code></dt>
<dd></dd>
<dt><code><strong>fontFamily</strong><span class="text-slate-400">?</span>: CSSProperties['fontFamily']</code></dt>
<dd></dd>
<dt><code><strong>fontSize</strong><span class="text-slate-400">?</span>: CSSProperties['fontSize']</code></dt>
<dd></dd>
<dt><code><strong>fontStyle</strong><span class="text-slate-400">?</span>: CSSProperties['fontStyle']</code></dt>
<dd></dd>
<dt><code><strong>fontWeight</strong><span class="text-slate-400">?</span>: CSSProperties['fontWeight']</code></dt>
<dd></dd>
<dt><code><strong>letterSpacing</strong><span class="text-slate-400">?</span>: CSSProperties['letterSpacing']</code></dt>
<dd></dd>
<dt><code><strong>lineHeight</strong><span class="text-slate-400">?</span>: CSSProperties['lineHeight']</code></dt>
<dd></dd>
<dt><code><strong>minHeight</strong><span class="text-slate-400">?</span>: CSSProperties['minHeight']</code></dt>
<dd></dd>
<dt><code><strong>paddingBottom</strong><span class="text-slate-400">?</span>: CSSProperties['paddingBottom']</code></dt>
<dd></dd>
<dt><code><strong>paddingTop</strong><span class="text-slate-400">?</span>: CSSProperties['paddingTop']</code></dt>
<dd></dd>
<dt><code><strong>textDecoration</strong><span class="text-slate-400">?</span>: CSSProperties['textDecoration']</code></dt>
<dd></dd>
</dl>